Manolo
Manolo

Reputation: 1676

Wrapping thread based async to task based async

How do I wrap code like below in a Task based async method?

void ExecuteThreadedAsync(Action a) {
   ThreadPool.QueueUserWorkItem(x=> 
   {
      action();
   });
}

Currently, this method is called like:

void Method() {
  var context = GetSomeContext();
  ExecuteThreadedAsync(() => 
  {
     var result = TimeConsumingWebServiceCall();
     context.Result = result;
  });
}

But what I want is something like:

async void Method() {
  var context = GetSomeContext();
  await ExecuteTaskBasedAsync(() => 
  {
     var result = TimeConsumingWebServiceCall();
     context.Result = result;
  });
}

Or:

async void Method() {
  var context = GetSomeContext();
  var result = await ExecuteTaskBasedAsync<Result>(() => 
  {
    var result = TimeConsumingWebServiceCall();
    return result;
  });
  context.Result = result;
}

Upvotes: 1

Views: 819

Answers (3)

YK1
YK1

Reputation: 7612

It depends on what the method TimeConsumingWebServiceCall() does - from its name, I infer that it calls a web service and time consuming happens due to slow response from the service. This is a IO bound task. Using ThreadPool.QueueUserWorkItem or Task.Run to synchronously call IO bound task is generally an anti-pattern. You are just offloading work to another thread which is anyway going to synchronously block on the web service call.

Task.Run is more suitable to offload compute bound tasks.

In your case, you should investigate how the TimeConsumingWebServiceCall actually calls the web service and you should use asynchronous IO API's.

  1. Are you using WCF? Choose to Generate task-based operations while creating proxy (Add Service Reference).
  2. Are you using System.Net.WebClient? Switch to the new System.Net.Http.HttpClient - start using its asynchronous methods like GetStringAsync or GetStreamAsync.

This way you can actually leverage the benefit of asynchronous API without un-necessarily blocking threads.

async Task Method() {
  var context = GetSomeContext();
  var result = await TimeConsumingWebServiceCallAsync();
  context.Result = result;
}

async Task TimeConsumingWebServiceCallAsync() {
    var httpClient = new HttpClient();
    var results = await httpClient.GetStringAsync(url); // or await wcfProxy.YourWCFMethodAsync();         
    // do processing if necessary
    return results;
}

Upvotes: 3

Alex.F
Alex.F

Reputation: 6181

This is what you can do in order to wrap a TimeConsumingWebServiceCall so that it can be executed asynchronously

async void MethodAsync()
{
    var context = GetSomeContext();
    context.Result = await Task.Factory.StartNew(() => 
        {
            return TimeConsumingWebServiceCall();
        });
}

A bit more advanced approach

async void Method()
{
    var TimeConsumingTask = Task.Factory.StartNew(() => 
        {
            return TimeConsumingWebServiceCall();
        });
    var context = GetSomeContext();
    context.Result = await TimeConsumingTask;
}

The reason behind this is to start the TimeConsumingTask even before you invoke GetSomeContext() as it can be executed parallely to TimeConsumingTask.
When GetSomeContext() finishes you await for the TimeConsumingTask to finish to assign it's result.

Upvotes: 0

Kenneth
Kenneth

Reputation: 28737

You can use the static Task.Run method:

async void Method() {
  var context = GetSomeContext();
  await Task.Run(() => 
  {
     var result = TimeConsumingWebServiceCall();
     context.Result = result;
  });
}

or

async void Method() {
  var context = GetSomeContext();
  context.Result = await Task.Run(() => TimeConsumingWebServiceCall());
}

Upvotes: 0

Related Questions