1

I have the following code that uploads a file to server and updates the progress of the upload in a bar.

private void UploadButton_Click(object sender, EventArgs e)
{
    Cursor = Cursors.WaitCursor;
    try
    {
        // get some info about the input file
        System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileTextBox.Text);
        UploadDocument(fileInfo);
        // show start message
        LogText("Starting uploading " + fileInfo.Name);
        LogText("Size : " + fileInfo.Length);
    }
    catch (Exception ex)
    {
        LogText("Exception : " + ex.Message);
        if (ex.InnerException != null) LogText("Inner Exception : " + ex.InnerException.Message);
    }
    finally
    {
        Cursor = Cursors.Default;
    }
}

private async void UploadDocument(System.IO.FileInfo fileInfo)
{
    var someTask = await Task.Run<bool>(() =>
    {
        // open input stream
        using (System.IO.FileStream stream = new System.IO.FileStream(FileTextBox.Text, System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            using (StreamWithProgress uploadStreamWithProgress = new StreamWithProgress(stream))
            {
                uploadStreamWithProgress.ProgressChanged += uploadStreamWithProgress_ProgressChanged;

                // start service client
                FileTransferWCF.FileTransferServiceClient client = new FileTransferWCF.FileTransferServiceClient();
                //FileTransferClient.FileTransferServiceClient client = new FileTransferClient.FileTransferServiceClient();

                // upload file
                client.UploadFile(fileInfo.Name, fileInfo.Length, uploadStreamWithProgress);

                LogText("Done!");

                // close service client
                client.Close();
            }
        }

        return true;
    });
}

void uploadStreamWithProgress_ProgressChanged(object sender, StreamWithProgress.ProgressChangedEventArgs e)
{
    if (e.Length != 0)
        progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);
}

Im getting the error: "Cross-thread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on." in the line:

progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);

Maybe Im doing this wrong. I'm new to Task Library in .Net.

Any clue?

svick
  • 236,525
  • 50
  • 385
  • 514
VAAA
  • 14,531
  • 28
  • 130
  • 253

2 Answers2

2

I recommend reading my async/await intro. One of the guidelines for async programming is to avoid async void; that is, use async Task instead of async void unless you're writing an event handler.

Also, once you start using async, try to use it everywhere. It really simplifies the code.

So, your code could be changed like this (assuming StreamWithProgress uses EAP conventions):

private async void UploadButton_Click(object sender, EventArgs e)
{
  UploadButton.Enabled = false;
  Cursor = Cursors.WaitCursor;
  try
  {
    // get some info about the input file
    System.IO.FileInfo fileInfo = new System.IO.FileInfo(FileTextBox.Text);
    var task = UploadDocument(fileInfo);

    // show start message
    LogText("Starting uploading " + fileInfo.Name);
    LogText("Size : " + fileInfo.Length);

    await task;

    LogText("Done!");
  }
  catch (Exception ex)
  {
    LogText("Exception : " + ex.Message);
    if (ex.InnerException != null) LogText("Inner Exception : " + ex.InnerException.Message);
  }
  finally
  {
    Cursor = Cursors.Default;
    UploadButton.Enabled = true;
  }
}

private async Task UploadDocument(System.IO.FileInfo fileInfo)
{
  // open input stream
  using (System.IO.FileStream stream = new System.IO.FileStream(FileTextBox.Text, System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read, 4096, true))
  { 
    using (StreamWithProgress uploadStreamWithProgress = new StreamWithProgress(stream))
    {
      uploadStreamWithProgress.ProgressChanged += uploadStreamWithProgress_ProgressChanged;

      // start service client
      FileTransferWCF.FileTransferServiceClient client = new FileTransferWCF.FileTransferServiceClient();

      // upload file
      await client.UploadFileAsync(fileInfo.Name, fileInfo.Length, uploadStreamWithProgress);

      // close service client
      client.Close();
    }
  }
}

void uploadStreamWithProgress_ProgressChanged(object sender, StreamWithProgress.ProgressChangedEventArgs e)
{
  if (e.Length != 0)
    progressBar1.Value = (int)(e.BytesRead * 100 / e.Length);
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Don't you want to `await` the task you have for `UploadDocument` at some point, before the `finally` is run? – Servy Feb 25 '13 at 17:49
  • 1
    I would even think that, given the refactor, the `LogText("Done!");` really belongs in `UploadButton_Click`, thus making `UploadDocument` able to be independent of the UI (have the textbox text be another parameter) which would allow it to be moved outside of the UI class entirely, and into a worker class of some sort. – Servy Feb 25 '13 at 17:56
  • Another good point. :) – Stephen Cleary Feb 25 '13 at 18:10
0

You need to make UI updates on the UI thread.

progressBar1.Invoke(new Action(() => 
    { progressBar1.Value = (int)(e.BytesRead * 100 / e.Length); }));

or alternatively (if you don't want to block until the method returns)

progressBar1.BeginInvoke(new Action(() => 
    { progressBar1.Value = (int)(e.BytesRead * 100 / e.Length); }));

Apologies for the syntax, I don't have access to VS at the moment.

FlyingStreudel
  • 4,434
  • 4
  • 33
  • 55
  • 1
    Even better, you can use `BeginInvoke` since you don't need to wait for the result of the invocation. – Marek Dzikiewicz Feb 25 '13 at 16:33
  • @MarekDzikiewicz so it would go like: progressBar1.BeingInvoke(new Action(() => { progressBar1.Value = (int)(e.BytesRead * 100 / e.Length); })); ?? – VAAA Feb 25 '13 at 16:45
  • 1
    Yes, the method has the same signature, it just doesn't block until the delegate executes like `Invoke` does. – FlyingStreudel Feb 25 '13 at 17:04