-2

I have the following method:

 public async Task<byte[]> MyMethod(byte[] my_byte_array)
 {
    //modify my_byte_array ...


    return  my_modified_byte_array;
     
 }

Now my problem is, I need to call MyMethod inside BackgroundWorker's DoWork as follows:

private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
        
    //somewhere in BW
    byte[] my_new_byte_array = MyMethod(my_byte_array);

}

But then I get an error Cannot implicitly convert type 'System.Threading.Tasks.Task<byte[]>' to 'byte[]'. When I read about it the solutions directing to change the calling function to asycn. But I am not able to make BackgroundWorker asycn. Is this issue unsolvable? Shifting from BW to other Task methods will cost me huge time.

pnatk
  • 121
  • 1
  • 7
  • 2
    I don't think it's advisable to sit on the fence and mix the two async styles. You don't have to convert all of your code to use it, just this one workflow i.e. all the code contained in this particular `BackgroundWorker`s `DoWork` and `RunWorkerCompleted` events – Charlieface Mar 21 '21 at 02:25
  • @pnatk The correct solution is probably to make whatever event starts the background worker `async void`, and then just call `await` your `MyMethod(my_byte_array)` there. – ProgrammingLlama Mar 21 '21 at 03:23
  • @Llama In my case BW is running continuously and needs to call MyMethod at each do while(true) loop. A button click event starts the BW and the call and return of MyMethod is needed to be performed inside BW like in my question. dovid's answer worked for me but I dont know why it got down votes. – pnatk Mar 21 '21 at 10:14
  • 2
    `BackgroundWorker` was created because there wasn't language support for asynchronous code to easily write asynchronous code. It was possible to write asynchronous code before and it's a lot easier to write it now with `async`-`await`. – Paulo Morgado Mar 21 '21 at 12:42
  • As a side note, the `BackgroundWorker` is [technologically obsolete](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker/64620920#64620920) IMHO. Not being able to handle asynchronous workloads is only one of its shortcomings. – Theodor Zoulias Mar 25 '21 at 16:22

3 Answers3

0

If the backgroundworker will only be working for several seconds (not minutes), consider to convert your complete backgroundworker to async method.

If depends a bit, what your backgroundworker does: is it using a lot of processing power because of calculations, or is it mostly waiting idly for external processes to finish, like reading a database, fetching items from the internet, writing a file, etc.

In the later case, you'll see that the backgroundworker uses a lot of methods that will probably also have an async version.

If that is the case, consider to change your background work into an async method.

Suppose your background work is like this:

private async void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    // get the input:
    List<string> fileNames = (string)e.Argument;
    List<int> processedProductIds = new List<int>();
    // process the input
    for (int i=0; i<fileNames.Count; ++i)
    {
        var products = ReadProductFile(fileNames[i]);
        var productIds = AddProductsToDatabase(products);
        processedProductIds.AddRange(productIds);

        // report progress
        var percentage = 100.0 * (double)i / (double)fileNames.Count;
        backgroundWorker.ReportProgress(percentage, fileName);
    }

    // return output
    e.Result = processedProductIds;
}

At its lowest level, the interface with the file reading and the database handling will have async methods. Consider to add async overloads for these method:

  • Declare the method async
  • Return Task instead of void, return Task<TResult> instead of TResult
  • Only exception: async event handlers return void instead of Task
  • Whenever you have to wait for another process, use the async method
  • Continue doing what you must do, until you need the result of the async method
  • To get the result of the async method, use await

For example:

public async IReadOnlyCollection<Product> ReadProductFileAsync(string fileName)
{
    List<Product> products = new List<Product>();
    using (var textReader = File.OpenText(fileName))
    {
        string line = await textReader.ReadLineAsync();
        while (line != null)
        {
            Product product = new Product(line);
            products.Add(product);
            line = await textReader.ReadLineAsync();
        }
    }
}

Change your DoWork to an async method. You don't have to use events to report progress:

public async Task<List<int>> DoWorkAsync(List<string> fileNames)
{
    List<int> processedProductIds = new List<int>();

    for (int i=0; i<fileNames.Count; ++i)
    {
        var products = await ReadProductFileAsync(fileNames[i]);
        var productIds = await AddProductsToDatabaseAsync(products);
        processedProductIds.AddRange(productIds);

        // report progress
        var percentage = 100.0 * (double)i / (double)fileNames.Count;
        ReportProgress(percentage, fileName);
    }
    return processedProductIds;
}

ReportProgress will for instance update a progressBar and write the processed file:

void UpdateProgress (int percentage, string fileName)
{
    this.progressBar.Value = percentage;
    this.ListBoxFileNames.Items.Add(fileName);
}

All work is done by the same thread. Well, not exactly, but the thread after the await has the same context, so it can act as if it was the original thread. Therefore no need for mutexes, InvokeRequired, etc.

The event handler that would start your backgroundworker is now also async.

this.button1.Clicked += OnButtonStartProcessing_ClickedAsync;

private async void OnButtonStartProcessing_ClickedAsync(object sender, ...)
{
    this.button1.Enabled = false;
    this.progressBar1.Value = 0;
    this.progressBar1.Visible = true;

    List<string> fileNames = this.FetchFilesNamesToProcess();
    List<int> processedIds = await ReadProductFilesAsync(fileNames);
    this.UpdateProcessedIds(processedIds);

    this.progressBar1.Visible = false;
    this.button1.Enabled = true;
}

If you have to do some heavy calculations, so no wait for other process, use Task.Run

// Warning, this procedure will take 5 seconds!
private int Calculate(int x, int y) {...}

private async Task<int> CalculateAsync(int x, int y)
{
    await Task.Run( () => this.Calculate(x, y));
}

Usage:

return await CalculateAsync(3, 4);

Finally: if you can do something useful instead of waiting for the other task to finish, don't await yet:

// Fetch the customer, do not await yet:
Task<int> taskA = FetchCustmer();

// because you didn't await, you can continue:
int b = DoSomethingElse();

// now you need the result for taskA
int a = await TaskA;
return a+b; 
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
-1

You can make your event handler async. Then use await to resolve the value.

private async void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
        
    //somewhere in BW
    byte[] my_new_byte_array = await MyMethod(my_byte_array);

}
Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
  • That is going to mark the BackgroundWorker as completed. Perhaps that's what OP needs, perhaps not, but not immediately obvious – Charlieface Mar 21 '21 at 01:20
  • @Charlieface Yes it does and having error when calling ProgressChanged. – pnatk Mar 21 '21 at 02:23
  • I get this error when the code calls ReportProgress inside DoWork: System.InvalidOperationException HResult=0x80131509 Message=This operation has already had OperationCompleted called on it and further calls are illegal. Can there be a remedy for that? – pnatk Mar 21 '21 at 10:29
  • Anis's answer looks interesting ismilar to dovid's https://stackoverflow.com/questions/28230363/operation-already-completed-error-when-using-progress-bar/28231354 – pnatk Mar 21 '21 at 10:38
-3
byte[] my_new_byte_array = MyMethod(my_byte_array).Result;
dovid
  • 6,354
  • 3
  • 33
  • 73