从任务/操作代理中更新UI的奇怪行为

时间:2014-05-19 22:49:42

标签: c# task-parallel-library async-await

我在Task内更新用户界面时遇到了一个奇怪的问题。下面的代码取自我的GetWorkingProxies方法,该方法基本上需要许多代理,ping它们并返回工作列表。

我正在使用Task.WhenAll创建并发性,因此它一次尽可能多地ping,而不是一次ping。

问题是,在Action委托中我更新了UI。以下是:

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 ...

1 个答案:

答案 0 :(得分:2)

我会做如下的最小改动。注意await SendAsync()的使用,现在连续在UI线程上异步发生,因此访问UI是安全的。此外,您不再需要使用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);