簡體   English   中英

C# 后台工作者 - 我應該同時使用多少個?

[英]C# Background Workers - How many should I use simultaneously?

我正在用 C# 編寫一個 MVVM (Caliburn.Micro) 應用程序,它使用 PowerShell 在遠程計算機上運行 WMI 查詢。 這些計算機是從選定的 Active Directory OU 加載的,因此可以有任意數量的計算機。 WMI 查詢的結果將顯示在 UI 上,我想同時運行多個查詢,並在查詢完成后立即顯示每個查詢。 我正在使用多個后台工作人員來實現這一點,目前它正在工作。 但是,我當前的代碼將為 OU 中的每台計算機創建一個后台工作人員,而沒有任何形式的隊列或限制。

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);
    }

}

我想如果所選 OU 中有足夠多的計算機,這可能會對性能產生很大的影響。 我應該將其限制為多少個同時運行的后台工作人員? 您會使用靜態數字還是基於 CPU 內核的數量?

另外,您將如何為此實現隊列? 我想過做這樣的事情:

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();
}

編輯:

嘗試使用任務並行庫

我從我的應用程序中采用了一種方法,該方法從遠程機器檢索登錄用戶並嘗試使用 TPL 而不是后台工作人員。 問題是它不是異步運行的,並且 UI 在運行時掛起。

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;
            }
        }
    });
}

我嘗試使方法asyncprivate async void GetLoggedOnUsersTPL()但這告訴我我需要使用await ,我不確定在這個例子中在哪里使用它。

編輯2:

使用任務並行庫的第二次嘗試

我現在正在嘗試使用 Task.Run 而不是 Parallel.ForEach 主要工作。 任務正在執行並且 UI 沒有掛起,但是如果我在所有任務完成之前從 TreeView 中選擇一個新的 OU,調試器會中斷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
            }
        }
    }
}

我正在使用多個后台工作人員來實現這一點,目前它正在工作。

BackgroundWorker是一種相當過時的類型,不能很好地處理動態需求。 如果您的工作負載是同步的(看起來是同步的),則Parallel是一種更好的方法。

問題是它不是異步運行的,並且 UI 在運行時掛起。

Parallel.ForEach是一個很好的解決方案。 要解鎖 UI,只需將其推送到線程池線程。 所以這個並行方法:

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

應該這樣稱呼:

await Task.Run(() => GetLoggedOnUsersTPL());

我有一個應用程序可以將財務記錄從一個總帳數據庫移動到 WPF 應用程序中的下一個數據庫。 每個操作都是獨立的,並在后台線程上運行,有時會恢復生機或處於休眠狀態,並向 wpf 應用程序的視圖報告,該應用程序盡職盡責地記錄了他們的實時狀態。

在測試期間,我的想法是最終限制總操作以確保平穩運行。

這種限制從未實現,我將應用程序發布到生產環境中,不同的人針對他們的特定模式運行應用程序。

所以我的建議是做類似的事情,你可以運行超過 200 個線程執行內部異步操作而不會出汗......一個特定的數字。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM