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.