3

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

所以我需要等待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
4

2 回答 2

3

我发现了几个问题:

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

b)只有当您使用 OpenReadAsync 创建请求时,才会触发OpenReadCompleted事件。它不会为 DownloadStringTaskAsync 触发。

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

strRet = await wc.DownloadStringTaskAsync(new Uri(strUrl));
于 2013-04-06T19:15:42.397 回答
2

您正在混合几种不同的异步 API。DownloadProgressChangedandOpenReadCompleted都是EAP 事件,whileDownloadStringTaskAsyncTAP 方法

我建议您始终使用 EAP API 或 TAP API。更好的是,从转换WebClientHttpClient.

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

于 2013-04-06T19:17:30.907 回答