【问题标题】:WebClient.DownloadDataAsync isn't truly async?WebClient.DownloadDataAsync 不是真正的异步?
【发布时间】:2014-11-19 23:00:22
【问题描述】:

我需要从同一个页面异步获取数据,不要阻塞主线程。

我尝试使用 WebClient 类的 DownloadDataAsync 方法,但它的行为似乎不是真正的异步方式。

为了测试这个,我编写了代码

    private void button1_Click(object sender, EventArgs e)
    {
        checkLink_async();
        Thread.CurrentThread.Join(5000);

        checkLink_async();
        Thread.CurrentThread.Join(5000);

        checkLink_async();
        Thread.CurrentThread.Join(5000);
    }


    /// <summary>
    /// Check the availability of IP server by starting async read from input sensors.
    /// </summary>
    /// <returns>Nothing</returns> 
    public void checkLink_async()
    {
        string siteipURL = "http://localhost/ip9212/getio.php";
        Uri uri_siteipURL = new Uri(siteipURL);

        // Send http query
        WebClient client = new WebClient();
        try
        {
            client.DownloadDataCompleted += new DownloadDataCompletedEventHandler(checkLink_DownloadCompleted_test);
            client.DownloadDataAsync(uri_siteipURL);

            tl.LogMessage("CheckLink_async", "http request was sent");
        }
        catch (WebException e)
        {
            tl.LogMessage("CheckLink_async", "error:" + e.Message);
        }
    }

    private void checkLink_DownloadCompleted_test(Object sender, DownloadDataCompletedEventArgs e)
    {
        tl.LogMessage("checkLink_DownloadCompleted", "http request was processed");
    }

日志结果:

01:32:12.089 CheckLink_async           http request was sent
01:32:17.087 CheckLink_async           http request was sent
01:32:22.097 CheckLink_async           http request was sent
01:32:27.102 checkLink_DownloadComplet http request was processed
01:32:27.102 checkLink_DownloadComplet http request was processed
01:32:27.102 checkLink_DownloadComplet http request was processed

我希望每个启动的 DownloadDataAsync 方法都将并行运行并在主线程代码运行期间完成(我在代码中使用 Thread.CurrentThread.Join 对此进行了模拟)。 但似乎在 button1_Click 结束之前,DownloadDataAsync 调用都没有完成(尽管有足够的时间)。

有什么方法可以改变这种行为,或者我应该使用其他方法吗?

【问题讨论】:

    标签: c# multithreading asynchronous webclient


    【解决方案1】:

    对于您所观察到的内容有一个简单的(r) 解释。 DownloadDataAsync 在幕后使用System.ComponentModel.AsyncOperation,它在DownloadDataAsync 操作开始时保留对SynchronizationContext 的引用,并在完成时回发给它。这可确保在具有有效 SynchronizationContext 的应用程序中启动下载的同一线程上引发 DownloadDataCompleted 事件(即,您正在使用的 Windows 窗体)。

    由于我们要回发到一个已经被阻塞的线程,完成处理程序必须等待它变得可用才能执行。

    在您的button1_Click 处理程序的开头引入SynchronizationContext.SetSynchronizationContext(null);,您将看到在按钮处理程序运行完成之前完成按照您最初的预期执行。

    编辑

    正如@PeterDuniho 所指出的,SetSynchronizationContext(null) 不应在快速和肮脏测试之外使用。证明围绕存在或不存在 SynchronizationContext 的理论的更好方法是使用单独的测试应用程序(即,如果已经使用 Windows 窗体,请启动一个快速控制台项目以查看您的代码在没有 @987654331 的情况下如何运行@)。玩弄SyncronizationContext.Current 的用途有限,会让你陷入痛苦的世界。

    【讨论】:

    • 详细说明:注意所有的完成都是同时出现的。这清楚地表明所有操作都已实际完成,只是在您通过从 button1_Click() 方法返回来解除对 UI 线程的阻塞之前,无法引发完成事件。另外,要明确一点:您应该将上下文设置为null,仅作为对基里尔理论的快速测试......不要将其留在生产代码中。
    • @PeterDuniho,我正要编辑我的答案以强调SetSynchronizationContext(null) 的危险,但你打败了我。 100% 同意。
    • 感谢您的回答!这种行为的原因现在很清楚了。引入 SynchronizationContext.SetSynchronizationContext(null); 之后异步操作按预期工作:11:37:47.897 CheckLink_async http 请求已发送 11:37:47.898 checkLink_DownloadComplet http 请求已处理 11:37:52.900 CheckLink_async http 请求已发送
    • 但是正如你所说,如果这只能用于测试,那么生产代码应该使用什么方法?当主线程监听外部事件时,我需要并行后台数据下载。唯一的方法是使用通常的同步 WebClient.DownloadData 操作转移到真正的多线程(ThreadPool 或类似)?
    【解决方案2】:

    我认为您最好将WebClientTask API 与async-await 一起使用,这会使您的代码更整洁:

    private async void button1_Click(object sender, EventArgs e)
    {
        await checkLink_async();  
        await checkLink_async();
        await checkLink_async();        
    }
    
    
    /// <summary>
    /// Check the availability of IP server by starting async read from input sensors.
    /// </summary>
    /// <returns>Nothing</returns> 
    public async Task checkLink_async()
    {
        string siteipURL = "http://localhost/ip9212/getio.php";
        // Send http query
        var client = new System.Net.WebClient();
        try
        {            
            Task t = client.DownloadDataTaskAsync(siteipURL);
    
            //tl.LogMessage("CheckLink_async", "http request was sent");
    
            await t;
    
            //tl.LogMessage("checkLink_DownloadCompleted", "http request was processed");
        }
        catch (System.Net.WebException e)
        {
           // tl.LogMessage("CheckLink_async", "error:" + e.Message);
        }
    }
    

    【讨论】:

    猜你喜欢
    • 2018-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多