WebClient.DownloadDataAsync 并不是真正的异步

本文关键字:异步 DownloadDataAsync 并不是 WebClient | 更新日期: 2023-09-27 18:31:11

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

我尝试使用类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 在代码中模拟了这一点)。但似乎两个 DownloadDataAsync 调用都没有在button1_Click结束之前完成(尽管有足够的时间)。

有没有办法改变这种行为,或者我应该使用另一种方法?

WebClient.DownloadDataAsync 并不是真正的异步

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

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

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

编辑

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

我认为你最好使用 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);
    }
}