Ali Tor
Ali Tor

Reputation: 3005

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

I have a Download method and ProgressChangedEventArgs carrying Progress property. The Download method must async to keep UI responsive. So I prepared the code below but in Form.cs calling e.Progress throws cross thread operation exception. Am I doing wrong at implementing async and await?

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");
    }

How can I fix this problem?

Upvotes: 0

Views: 316

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457302

First, you shouldn't use async void except for event handlers.

Second, progress updates should be done via IProgress<T>. This permits you to use Progress<T> is your progress update consumer, which does thread transitions for you.

Third, use asynchronous APIs instead of Task.Run. You could use HttpClient instead of the rather dated 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);
    }
  }
}

Usage:

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);
}

Upvotes: 3

Related Questions