Я пишу приложение 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
}
}
}
}