C# Background Workers - Сколько я должен использовать одновременно? - PullRequest
0 голосов
/ 26 января 2020

Я пишу приложение MVVM (Caliburn.Micro) в C#, которое использует PowerShell для выполнения запросов WMI на удаленных компьютерах. Компьютеры загружаются из выбранного подразделения Active Directory, поэтому их может быть любое количество. Результаты запросов WMI будут отображаться в пользовательском интерфейсе, и я хочу выполнить несколько запросов одновременно и отображать каждый из них, как только его запрос будет завершен. Я использую несколько фоновых работников для достижения этой цели, и на данный момент это работает. Однако мой текущий код создаст одного фонового рабочего для каждого компьютера в подразделении без какой-либо формы очереди или ограничения.

private void QueryComputers()
{
    foreach (RemoteComputer computer in Computers)
    {
        BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerReportsProgress = true;
        bw.DoWork += BackgroundWorker_DoWork;
        bw.ProgressChanged += BackgroundWorker_ProgressChanged;
        bw.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
        bw.RunWorkerAsync(computer.DNSHostName);
    }

}

Я полагаю, если в выбранном подразделении было достаточно компьютеров, чтобы это могло иметь большую производительность влияние. Сколько одновременных фоновых работников я должен ограничить это? Будете ли вы использовать число * stati c или основывать его на количестве ядер ЦП?

Кроме того, как бы вы реализовали очередь для этого? Я думал о том, чтобы сделать что-то вроде этого:

private int bwCount = 0;
private int bwLimit = 5; // 10, 20, 200??

private void QueryComputers()
{
    int stopAt = lastIndex + (bwLimit - bwCount);
    if (stopAt > Computers.Count - 1) stopAt = Computers.Count - 1;
    if (stopAt > lastIndex)
    {
        for (int i = lastIndex; i <= lastIndex + (bwLimit - bwCount); i++) {
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerReportsProgress = true;
            bw.DoWork += BackgroundWorker_DoWork;
            bw.ProgressChanged += BackgroundWorker_ProgressChanged;
            bw.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
            bw.RunWorkerAsync(Computers[i].DNSHostName);

            lastIndex = i;
            bwCount++;
        }
    }
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Handle Result etc...

    bwCount--;
    QueryComputers();
}

РЕДАКТИРОВАТЬ:

Попытка использования параллельной библиотеки задач

I Я выбрал один метод из моего приложения, который извлекает пользователя, вошедшего в систему, с удаленного компьютера и пытается использовать TPL вместо фонового работника. Проблема в том, что он не работает асинхронно, и пользовательский интерфейс зависает во время работы.

private void GetLoggedOnUsersTPL()
{
    Parallel.ForEach(Computers, (computer) =>
    {
        using (PowerShell ps = PowerShell.Create())
        {

            computer.Status = RemoteComputer.ComputerStatus.UpdatingStatus;

            // Ping the remote computer to check if it's available to connect to
            ps.AddScript($"Test-Connection -ComputerName {computer.DNSHostName} -Count 1 -Quiet");
            Collection<PSObject> psOutput = ps.Invoke();
            if ((bool)psOutput[0].BaseObject) // If the computer responded to the Ping
            {
                ps.Commands.Clear(); // Remove the Test-Connection (Ping) command

                // Use a WMI query to find out who is logged on to the remote computer
                ps.AddScript($"Get-CimInstance -ComputerName {computer.DNSHostName} -Class Win32_ComputerSystem -Property UserName");
                psOutput = ps.Invoke();

                if (psOutput.Count < 1) // If there are no results we will try using a DCOM connection instead of WSMAN
                {
                    ps.Commands.Clear();
                    ps.AddScript("$opt = New-CimSessionOption -Protocol DCOM");
                    ps.AddScript($"$cims = New-CimSession -ComputerName {computer.DNSHostName} -SessionOption $opt");
                    ps.AddScript($"Get-CimInstance -Class Win32_ComputerSystem -Property UserName -CimSession $cims");
                    psOutput = ps.Invoke();
                }

                if (psOutput.Count > 0) // Check if we had any results
                {
                    string userName = psOutput[0].Members["UserName"].Value.ToString();
                    if (userName == null || userName == "")
                    {
                        computer.LoggedOnUser = "Nobody is logged on...";
                        computer.Status = RemoteComputer.ComputerStatus.Online;
                    }
                    else
                    {
                        computer.LoggedOnUser = userName;
                        computer.Status = RemoteComputer.ComputerStatus.Online;

                    }
                }
                else
                {
                    computer.Status = RemoteComputer.ComputerStatus.Blocked;
                }

            }
            else
            { 
                computer.Status = RemoteComputer.ComputerStatus.Offline;
            }
        }
    });
}

Я попытался сделать метод async Т.е. private async void GetLoggedOnUsersTPL(), но это сказало мне, что мне нужно использовать await, и Я не уверен, где использовать это в этом примере.

РЕДАКТИРОВАТЬ 2:

Вторая попытка использования библиотеки параллельных задач

Я сейчас пытаюсь использовать Task.Run вместо Parallel.ForEach, который работает в основном. Задачи выполняются, и пользовательский интерфейс НЕ зависает, но если я выберу новый OU из TreeView до того, как все задачи завершат выполнение разрывов отладчика в строках token.ThrowIfCancellationRequested();, поэтому они не будут перехвачены. Кто-нибудь может указать, что я здесь сделал неправильно, пожалуйста?

public override bool IsSelected // << AD OU IsSelected in TreeView
{
    get { return isSelected; }
    set
    {
        if (isSelected != value)
        {
            isSelected = value;

            if (getLoggedOnUsersTokenSource != null) // If any 'GetLoggedOnUsers' tasks are still running, cancel them
            {
                getLoggedOnUsersTokenSource.Cancel(); 
            }

            LoadComputers(); // Load computers from the selected OU
            GetLoggedOnUsersTPL();
        }
    }
}

private CancellationTokenSource getLoggedOnUsersTokenSource;
private async void GetLoggedOnUsersTPL()
{
    getLoggedOnUsersTokenSource = new CancellationTokenSource();
    CancellationToken token = getLoggedOnUsersTokenSource.Token;

    List<Task> taskList = new List<Task>();
    foreach (RemoteComputer computer in Computers)
    {
        taskList.Add(Task.Run(() => GetLoggedOnUsersTask(computer, token), token));

    }

    try
    {
        await Task.WhenAll(taskList);
    } catch (OperationCanceledException) // <<<< Not catching all cancelled exceptions
    {
        getLoggedOnUsersTokenSource.Dispose();
    }

}

private void GetLoggedOnUsersTask(RemoteComputer computer, CancellationToken token)
{
    using (PowerShell ps = PowerShell.Create())
    {
        if (token.IsCancellationRequested)
        {
            token.ThrowIfCancellationRequested();
        }

        // Ping remote computer to check if it's online

        if ((bool)psOutput[0].BaseObject) // If the computer responded to the Ping
        {
            if (token.IsCancellationRequested)
            {
                token.ThrowIfCancellationRequested();
            }

            // Run WMI query to get logged on user using WSMAN

            if (psOutput.Count < 1) // If there were no results, try DCOM
            {

                if (token.IsCancellationRequested)
                {
                    token.ThrowIfCancellationRequested();
                }

                // Run WMI query to get logged on user using DCOM

                // Process results
            }
        }
    }
}

Ответы [ 2 ]

2 голосов
/ 28 января 2020

Я использую несколько фоновых рабочих для достижения этой цели, и на данный момент она работает.

BackgroundWorker - это довольно устаревший тип, который не поддерживает динамические c требования хорошо. Parallel - лучший подход, если ваша рабочая нагрузка является синхронной (что, по-видимому, является).

Проблема заключается в том, что она не работает асинхронно, а пользовательский интерфейс зависает во время работы.

Parallel.ForEach - отличное решение. Чтобы разблокировать пользовательский интерфейс, просто вставьте его в поток пула потоков. Таким образом, этот параллельный метод:

private void GetLoggedOnUsersTPL()
{
  Parallel.ForEach(Computers, (computer) =>
  {
    ...
  });
}

должен называться так:

await Task.Run(() => GetLoggedOnUsersTPL());
1 голос
/ 26 января 2020

У меня было приложение для перемещения финансовых записей из одной базы данных Главной книги в другую в приложении WPF. Каждая операция была независимой и выполнялась в фоновом потоке, а иногда возникала или оставалась бездействующей и возвращалась к представлению приложения wpf, которое должным образом записывало их живые состояния.

Во время тестирования я думал, что, в конечном итоге, все операции будут сокращаться, чтобы обеспечить плавность операций.

Это регулирование было никогда реализовано, и я выпустил приложение в производство, где работали разные люди. приложение, ориентированное на их указанные c схемы.

Так что мой совет - делать то же самое, вы можете запускать более 200 потоков, выполняя внутренние асинхронные операции, не прерывая работу ... так что это зависит от нагрузки операций и того, что они делают, которые имеют больше отношение к этому решению, чем конкретное c число.

...