4

I'm reading left and right about Async and Task but I still don't get how one can wrap an existing 3rd party call into an async method.

Mainly the 3rd party dll offers a method to call one of their APIs

var client = new FooClient();
var response = client.CallMethod(param1, message);

if (response.RestException != null)
{
    status = response.RestException.Message;
    return false;
}
else 
   return true

and this call as expected will block the current thread till it returns the response.

So I looked at Task and other ways to make this call Async but still get the response back cause based on that the application will take different actions.

I kind of feel Task is the way to go but their DLL does NOT have an CallMethodAsSync method so I don't know what to do at this point.

Any help will be much appreciated !!

Johnny
  • 601
  • 6
  • 18
  • Note that if the 3rd party API did not provide any truly async methods just putting the call on a separate thread will not give you any kind of performance boosts. What library are you working with, there may be async patterns but you don't recognise them. (for example [they could use patterns EAP or APM instead of the TAP pattern that you are looking for](http://msdn.microsoft.com/en-us/library/hh873178%28v=vs.110%29.aspx)) – Scott Chamberlain Nov 13 '13 at 17:34
  • It's the twilio-csharp library. I guess I'm still confused on how async works. I was under the impression that the async will spawn another thread and release the current one. This should make no difference in one user based but yes with 1000s (or at least so they say). – Johnny Nov 13 '13 at 17:44
  • Mainly I'm writing an API that does various things and among those, also send TXT message via the Twilio APIs. Depending on the success of the SMS, the code takes different path so that's why I need to wait for the API answer and I was hoping this to be async. – Johnny Nov 13 '13 at 17:48
  • `Async != Extra threads`. What async means is concurrent work is happening, wheather that work is done on a extra thread, a external process, a I/O Completion port, is all beneath the asynchronous abstraction. – Scott Chamberlain Nov 13 '13 at 17:53
  • Right, but it's not happening on the same thread that started the async call, right? – Johnny Nov 13 '13 at 17:56

1 Answers1

6

The simplest way to do this is to just dump the call into a Task.Run, and then await the return of the Task.Run. This will offload the intensive work to another thread, allowing the UI thread to continue. Here's a simple example of a method that has to wait for another time-intensive method before it can return:

    static void Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

    static bool BlockingMethod(string someText)
    {
        Thread.Sleep(2000);
        return someText.Contains("SomeOtherText");
    }

As we can see, the BlockingMethod method has a Thread.Sleep(2000) as its first statement. This means that the thread running the calling method Main has to wait a full two seconds before it can get the result of BlockingMethod. If the thread running the Main method is dealing with UI repainting, then this means that we get a UI that appears unresponsive/locked for a full two seconds. We can offload the work of waiting for BlockingMethod onto another thread like so:

First we mark our calling method as 'async' because this tells the compiler to generate something resembling a state machine for all of the 'await's in the async method:

    static async void Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

Next we turn the return type 'void' into Task. This allows it to be awaited by other threads (you can leave it as async void if you don't care about that, but async methods with a return type need to return Task):

    static async Task Main()
    {
        Console.WriteLine("Begin");
        var result = BlockingMethod("Hi!");
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

Now our method is allowed to run asynchronously, and it's allowed to be awaited by other methods. But this hasn't solved our problem yet, because we're still synchronously waiting for the blocking method. So we move our blocking method out to another thread by calling Task.Run, and awaiting its result:

    static async Task Main()
    {
        Console.WriteLine("Begin");
        await Task.Run(() => BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

But this presents us with a problem: our blocking method was returning something that we wanted to use later on in our method, but Task.Run returns void! In order to get access to the variable returned by the blocking method running in the other thread, we have to capture a local variable in a closure:

    static async Task Main()
    {
        Console.WriteLine("Begin");
        bool result = true; 
        await Task.Run(() => result = BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

So, in summary, what we did was we took a normal synchronous method (the calling method that we can rewrite, rather than the third party API that we can't rewrite) and we changed it so that it was marked as async and it returned a Task. If we wanted it to return a result, then it'd need to be a generically typed Task. After that, we wrapped the call to the blocking method in a Task.Run -- which creates a new thread for the blocking method to run on -- then we gave it an Action (in lambda syntax) to run. In that Action, we referenced a variable defined in the calling function (this is the closure) which can capture the result of the blocking method.

Now, we're asynchronously waiting for a synchronous, blocking method somewhere else. It doesn't matter that the blocking method isn't inherently asynchronous, because we're letting it run synchronously in another thread, and allowing our thread to just await its result.

If any of this was unclear, then please comment. Asynchrony is a bit confusing at first, but it's a godsend for responsive UIs.

EDIT:

In response to a comment by Scott Chamberlain, Task.Run also has an overload which returns the type of the method its running (supplying it a Func, rather than an Action). So we can simply use:

    static async Task MainAsync()
    {
        Console.WriteLine("Begin");
        bool result = await Task.Run(() => BlockingMethod("Hi!"));
        Console.WriteLine("Result: " + result);
        Console.ReadLine();
    }

Keep in mind -- as Scott Chamberlain points out -- that there's no inherent performance benefit to running work in another thread. In many cases, it may actually cause worse performance, as setting up and tearing down threads is expensive. Task-based asynchrony is only useful to keep a busy thread (e.g. the UI thread) unblocked, so it can respond to requests, or when properly sub-dividing work through use of e.g. Parallel.ForEach.

Ben H
  • 494
  • 3
  • 10
  • 3
    You are looking at the wrong overload of Task.Run `var result = await Task.Run(() => BlockingMethod("Hi!"));` is perfectly fine to do. – Scott Chamberlain Nov 13 '13 at 18:12
  • Thanks for the comment @Scott Chamerlain. I didn't realise that Task.Run had an overload which provided a generic return. Thanks for that (secretly I'm now upset that I get to say closure less often). – Ben H Nov 13 '13 at 18:16
  • What if result was a var of type (FooType)? the compiler tells me that it needs to be initialized. I see that's why you had = true on your initialization. So it would work if I just create a Foottype result = new FooType(); ...right? – Johnny Nov 13 '13 at 18:16
  • One think I would like to point out, in your example `async` gets you nothing other than making the program run slower because this is a console application. If you had a [`SynchronizationContext`](http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext%28v=vs.110%29.aspx) (a message pump with a UI of some kind) then you could potentially see some benefit by allowing other things to process, but if you are doing no work while you await, you should not be using `Task.Run` to make a async method. – Scott Chamberlain Nov 13 '13 at 18:17
  • @ScottChamberlain Well, in my case it's an WebAPI2 so I believe should make a difference, right? – Johnny Nov 13 '13 at 18:21
  • @Johnny, yes if you're using the closure route, then you'd initialize the variable to something first, e.g. FooType result = new FooType(), but you could use Scott Chamberlain's route instead, and just have FooType result = await Task.Run(() => SomeMethodWhichReturnsFoo()). – Ben H Nov 13 '13 at 18:22
  • @Johnny you're welcome, but please keep Scott's advice in mind: async is deceptively expensive, and it won't make slow code faster. It's meant to be used in cases where something is blocking a message pump, so unless the blocked thread is carrying out some sort of message pumping (e.g. processing events, notifying observers of changes etc), then it'll only serve to slow down your application. – Ben H Nov 13 '13 at 18:27
  • Yes, I'm currently reading Alex Davies book and learning from it. I already applied Scott's variation and it seems to work on my API. I'll run some tests soon. My other problem is using async with DatabaseFirst EF6 Stored Procedures cause apparently they don't come with an xxxAsAsync method yet. I'll see if this way around will make a difference there too. – Johnny Nov 13 '13 at 18:33
  • I have some interesting twist but I think I'll create a new Post to explain better. http://stackoverflow.com/questions/19962100/task-run-not-blocking-and-not-continuing-the-code-below – Johnny Nov 13 '13 at 18:57