如何在不调用的情况下使用 Form.cs 中的异步方法内部引发的 EventArgs?

How to use EventArgs raised inside an async method in Form.cs without invoking?

我有一个 Download 方法和 ProgressChangedEventArgs 携带 Progress 属性。 Download 方法必须异步才能保持 UI 响应。所以我准备了下面的代码,但是在 Form.cs 中调用 e.Progress 会抛出跨线程操作异常。我在实施 asyncawait 时做错了吗?

In DownloadManager.cs

    public async void DownloadProcedure(long contentLength)
    {
        await Task.Run(() =>
        {
            long totalBytesReceived = 0;
            int bytesRead = 0;
            byte[] buffer = new byte[10 * 1024];
            HttpWebRequest req = url.CreateHttpWebRequest();
            HttpWebResponse resp = req.GetHttpWebResponse();
            Stream remoteStream = resp.GetResponseStream();

            while ((bytesRead = remoteStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                totalBytesReceived += bytesRead;
                double newProgress = (totalBytesReceived * 100d / contentLength);
                if (progress != newProgress && ProgressChanged != null)
                {
                    ProgressChanged(this, new ProgressChangedEventArgs(progress));
                }
            }
        });
    }

In Form.cs

    void Form1_Load(object sender, EventArgs e)
    {
        DownloadManager dm = new DownloadManager("http://download.thinkbroadband.com/20MB.zip", "");

        long size = 0;
        bool res = false;
        dm.checkUrl(ref size, ref res);
        dm.ProgressChanged += dm_ProgressChanged;
        dm.DownloadProcedure(size);
    }

    void dm_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        Text = e.Progress.ToString("0.00");
    }

我该如何解决这个问题?

首先,您不应该使用 async void 除了事件处理程序。

其次,进度更新应该通过IProgress<T>完成。这允许您使用 Progress<T> 是您的进度更新消费者,它为您执行线程转换。

第三,使用异步API代替Task.Run。您可以使用 HttpClient 而不是过时的 HttpWebRequest:

private static readonly HttpClient client = new HttpClient();
public async Task DownloadProcedureAsync(long contentLength, IProgress<double> progress)
{
  long totalBytesReceived = 0;
  int bytesRead = 0;
  byte[] buffer = new byte[10 * 1024];

  using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
  using (var remoteStream= await response.Content.ReadAsStreamAsync())
  {
    while ((bytesRead = await remoteStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
    {
      totalBytesReceived += bytesRead;
      double newProgress = (totalBytesReceived * 100d / contentLength);
      progress?.Report(newProgress);
    }
  }
}

用法:

async void Form1_Load(object sender, EventArgs e)
{
  DownloadManager dm = new DownloadManager("http://download.thinkbroadband.com/20MB.zip", "");

  long size = 0;
  bool res = false;
  dm.checkUrl(ref size, ref res);
  var progress = new Progress<double>(p => { Text = e.ToString("0.00"); });
  await dm.DownloadProcedureAsync(size, progress);
}