【问题标题】:C# async method that also has anonymous callback handlers does not flow correctly也具有匿名回调处理程序的 C# 异步方法无法正确流动
【发布时间】:2013-04-07 06:07:49
【问题描述】:

以下是 C# async 方法的代码,该方法还具有两个 WebClient 控件事件的回调处理程序:DownloadProgressChangedOpenReadCompleted。当我运行代码时,最初它会流向“await DownloadStringTaskAsync()”调用并退出。然后我看到 DownloadProgressChanged 的匿名事件处理程序代码触发,这就是我遇到问题的地方。然后代码流向“return strRet”语句,因此该方法的返回值是分配给该方法顶部的 strRet 的初始化值“(none)”,而不是 OpenReadCompleted 匿名回调中分配给 strRet 的网页内容。

所以我需要等待 OpenReadCompleted 回调在控制流到 return 语句之前执行,但我不确定如何正确执行此操作。如何更正代码,使其在执行 OpenReadCompleted 回调之前不会到达“return strRet”语句?

    /// <summary>
    /// This method downloads the contents of a URL to a string.  Returns the URL contents
    ///  as a string if it succeeds, throws an Exception if not.
    /// <param name="strUrl">The URL to download.</param>
    /// <param name="progress">An IProgress object to report download progress to.  May be NULL.</param>
    /// <param name="cancelToken">A cancellation token. May be NULL.</param>
    /// <param name="iNumSecondsToWait">The number of seconds to wait before cancelling the download. Default is 30 seconds</param>
    /// </summary>
    /// <remarks>
    /// Use "await" with this method wrapped in Task.run() to manage the process asynchronously.
    /// 
    /// NOTE: The DownloadProgressChanged() event is raised on the UI
    ///  thread so it is safe to do UI updates from the IProgress.Report()
    ///  method.
    /// </remarks>
    async public static Task<string> URLToString(string strUrl, IProgress<int> progress, CancellationToken cancelToken, int iNumSecondsToWait = 30)
    {
        // The string to be returned.
        string strRet = "(none)";

        strUrl = strUrl.Trim();

        if (String.IsNullOrWhiteSpace(strUrl))
            throw new ArgumentException("(Misc::URLToString) The URL is empty.");

        if (iNumSecondsToWait < 1)
            throw new ArgumentException("(Misc::URLToString) The number of seconds to wait is less than 1.");

        // Asynchronous download.  Note, the Silverlight version of WebClient does *not* implement 
        //  IDisposable.
        WebClient wc = new WebClient();

        // Create a download progress changed handler so we can pass on progress
        //  reports to the caller if they provided a progress report object.
        //  This event is raised on the UI thread.
        wc.DownloadProgressChanged += (s, e) =>
        {
            // Do we have a progress report handler?
            if (progress != null)
                // Yes, call it.
                progress.Report(e.ProgressPercentage);

            // If we have a cancellation token and the operation was cancelled, then abort the download.
            if (cancelToken != null)
                cancelToken.ThrowIfCancellationRequested();

        }; // wc.DownloadProgressChanged += (s, e) =>

        //  Use a Lambda expression for the "completed" handler
        //  that writes the downloaded contents as a string to a file.
        wc.OpenReadCompleted += (s, e) =>
        {
            // If we have a cancellation token and the operation was cancelled, then abort the download.
            if (cancelToken != null)
                cancelToken.ThrowIfCancellationRequested();

            // Return the downloaded file as a string.
            strRet = e.Result.ToString();
        }; // wc.OpenReadCompleted += (s, e) =>

        // Now make the call to download the file and do an asynchronous wait for the result.
        await wc.DownloadStringTaskAsync(new Uri(strUrl));

        // wc.DownloadStringAsync(new Uri(strUrl));

        return strRet;
    } // async public static void URLToStr

=================================

更新:根据我收到的答案,我已将代码修改为以下内容:

    async public static Task<string> URLToStringAsync(string strUrl, IProgress<int> progress, CancellationToken cancelToken, int iNumSecondsToWait = 30)
    {
        strUrl = strUrl.Trim();

        if (String.IsNullOrWhiteSpace(strUrl))
            throw new ArgumentException("(Misc::URLToStringAsync) The URL is empty.");

        if (iNumSecondsToWait < 1)
            throw new ArgumentException("(Misc::URLToStringAsync) The number of seconds to wait is less than 1.");

        // Asynchronous download.  Note, the Silverlight version of WebClient does *not* implement 
        //  IDisposable.
        WebClient wc = new WebClient();

        // Create a download progress changed handler so we can pass on progress
        //  reports to the caller if they provided a progress report object.
        //  This event is raised on the UI thread.
        wc.DownloadProgressChanged += (s, e) =>
        {
            // Do we have a progress report handler?
            if (progress != null)
                // Yes, call it.
                progress.Report(e.ProgressPercentage);

            // If we have a cancellation token and the operation was cancelled, then abort the download.
            if (safeCancellationCheck(cancelToken))
                wc.CancelAsync();
        }; // wc.DownloadProgressChanged += (s, e) =>

        // Now make the call to download the file and do an asynchronous wait for the result.
        return await wc.DownloadStringTaskAsync(new Uri(strUrl));
    } // async public static void URLToStringAsync

【问题讨论】:

  • 我想在继续之前验证这一点 - 你能提供一个简短但完整的程序来演示这个问题吗?
  • @JonSkeet - 请参阅 outcoldman 对我的回答,因为它包含了我的问题的真正原因。

标签: c# .net asynchronous task async-await


【解决方案1】:

我发现了几个问题:

a) 从 MSDN 文档看来,DownloadStringTaskAsync 不会触发 DownloadProgressChanged

b) OpenReadCompleted 事件将仅在您使用 OpenReadAsync 创建请求时触发。它不会为 DownloadStringTaskAsync 触发。

c) 你可以使用DownloadStringCompleted 事件来获取 DownloadStringTaskAsync 的结果,但是为什么如果你使用 async/await 你可以这样做:

strRet = await wc.DownloadStringTaskAsync(new Uri(strUrl));

【讨论】:

  • 谢谢。就是这样,我很高兴,否则我对 async/await 的理解就会破裂。关于等待 DownloadStringTaskAsync 的好提示。
【解决方案2】:

您正在混合使用几种不同的异步 API。 DownloadProgressChangedOpenReadCompleted 都是EAP events,而DownloadStringTaskAsyncTAP method

我建议您始终使用 EAP API 或 TAP API。更好的是,将WebClient 转换为HttpClient

顺便说一句,您可能不想从事件处理程序调用ThrowIfCancellationRequested。相反,请将您的 CancellationToken 连接到 WebClient.CancelAsync

【讨论】:

  • 查看 outcoldman 对我的回答,因为它包含了我的问题的原因。不过,您也对您投了赞成票,因为您对不混合异步模式提出了很好的看法。感谢有关 HttpClient 的提示和有关 WebClient.CancelAsync 的提示。
  • 更新:我认为 Windows Phone 7 没有 HttpClient。 System.Net 没有它,我检查了我为 WP7 安装的 Micrsoft.Bcl.Async 包,所以我也找不到它。我猜我被 WebClient 卡住了,但至少我知道它有下载进度事件回调。
猜你喜欢
  • 2015-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 2016-09-06
相关资源
最近更新 更多