Я столкнулся с необычной проблемой с обновлением пользовательского интерфейса из Task
. Код, приведенный ниже, берется из моего метода GetWorkingProxies
который в основном принимает несколько прокси, пинги их и возвращает рабочий список.
Я использую Task.WhenAll
для создания параллелизма, поэтому он пингует как можно больше за один раз, а не по одному за раз.
Проблема заключается в том, что в делегате Action я обновляю пользовательский интерфейс. Что ниже:
Action<string> checkProxy = s => {
Ping ping = new Ping();
try {
string[] proxy = s.Split(':');
lblLog.Text = "Testing Proxy: " + proxy[0];
PingReply reply = ping.Send(proxy[0], Convert.ToInt32(proxy[1]));
if(reply.Status == IPStatus.Success)
{
workingProxies.Add(s);
lblSuccessProxies.Text = workingProxies.Count.ToString();
}
else
{
failedProxies.Add(s);
lblFailedProxies.Text = failedProxies.Count.ToString();
}
} catch(Exception ex)
{
// DEBUG
Console.WriteLine(ex);
}
};
И вот код, который создает массив Task
...
Task[] tasks = new Task[proxies.Count];
for (int i = 0; i < proxies.Count; i++)
{
string tmp = proxies[i];
tasks[i] = Task.Run(() => checkProxy(tmp));
}
await Task.WhenAll(tasks);
Я не могу понять, почему lblLog.Text = "Testing Proxy: " + proxy[0];
отлично работает, но lblFailedProxies.Text = failedProxies.Count.ToString();
и lblSuccessProxies.Text = workingProxies.Count.ToString();
оба генерируют System.InvalidOperationException
.
Я знаю из тестирования, что это проблема с перекрестными потоками, но как одно обновление UI может работать, но не другое из одного и того же делегата Action?
Почему это?
Редактировать:
Фактическое исключение:
A first chance exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
System.InvalidOperationException: Cross-thread operation not valid: Control 'lblSuccessProxies' accessed from a thread other than the thread it was created on.
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.set_WindowText(String value)
at System.Windows.Forms.Control.set_Text(String value)
at System.Windows.Forms.Label.set_Text(String value)
at SoundCloudPlays.Form1.<>c__DisplayClass12.<getProxies>b__10(String s) in ...
Я бы сделал минимальное изменение, как показано ниже. Обратите внимание на использование await SendAsync()
, теперь продолжения происходят асинхронно в потоке пользовательского интерфейса, поэтому он безопасен для доступа к пользовательскому интерфейсу. Кроме того, вам больше не нужно использовать Task.Run
:
Func<string, Task> checkProxyAsync = async(s) => {
Ping ping = new Ping();
try {
string[] proxy = s.Split(':');
lblLog.Text = "Testing Proxy: " + proxy[0];
PingReply reply = await ping.SendAsync(proxy[0], Convert.ToInt32(proxy[1]));
if(reply.Status == IPStatus.Success)
{
workingProxies.Add(s);
lblSuccessProxies.Text = workingProxies.Count.ToString();
}
else
{
failedProxies.Add(s);
lblFailedProxies.Text = failedProxies.Count.ToString();
}
} catch(Exception ex)
{
// DEBUG
Console.WriteLine(ex);
}
};
Использование:
Task[] tasks = new Task[proxies.Count];
for (int i = 0; i < proxies.Count; i++)
{
string tmp = proxies[i];
tasks[i] = checkProxyAsync(tmp);
}
await Task.WhenAll(tasks);
SendAsync
блокируется. Если это так, попробуйте изменить его следующим образом:PingReply reply = await Task.Run(() => ping.Send(proxy[0], Convert.ToInt32(proxy[1])));