I have done a lot of search on this topic, and I read most of the posts here on this site regarding this topic, however I'm still confused and I need a straight forward answer. Here is my situation:
I have an established Winform application that I can't make it all 'async'. I'm forced now to use an external library that is all written as async functions.
In my application I have
/// <summary>
/// This function I can't change it to an 'async'
/// </summary>
public void MySyncFunction()
{
//This function is my point in my application where I have to call the
//other 'async' functions but I can't change the function itself to 'async'
try
{
//I need to call the MyAsyncDriverFunction() as if it is a synchronous function
//I need the driver function to finish execution and return before processing the code that follows it
//I also need to be able to catch any exceptions
MyAsyncDriverFunction();
//Rest of the code have to wait for the above function to return
}
catch (Exception exp)
{
//Need to be able to handle the exception thrown
//from the MyAsyncDriverFunction here.
}
}
public static async Task<IEnumerable<string>> MyAsyncDriverFunction()
{
try
{
var strCollection = await AsyncExternalLibraryFunction1();
var strCollection2 = await AsyncExternalLibraryFunction2();
return strCollection;
}
catch (Exception exp)
{
//Need to be able to catch an exception and re-throw it to the caller function
}
}
As outlined in the code, I need to be able to:
- I can't change my MySyncFunction to an async
- Call the "MyAsyncDriverFunction" in a sync way, where it have to wait for it to finish all its work before I process the code that follows
- Be able to handle exceptions in both functions (from what I read so far this is tricky?)
- I need a simple way using the standard API, I can't use any third party library (even if I wanted to)
however I'm still confused and I need a straight forward answer.
That's because there isn't a "straight-forward" answer.
The only proper solution is to make MySyncFunction
asynchronous. Period. All other solutions are hacks, and there is no hack that works perfectly in all scenarios.
I go into full details in my recent MSDN article on brownfield async development, but here's the gist:
You can block with Wait()
or Result
. As others have noted, you can easily cause a deadlock, but this can work if the asynchronous code never resumes on its captured context.
You can push the work to a thread pool thread and then block. However, this assumes that the asynchronous work is capable of being pushed to some other arbitrary thread and that it can resume on other threads, thus possibly introducing multithreading.
You can push the work to a thread pool thread that executes a "main loop" - e.g., a dispatcher or my own AsyncContext
type. This assumes the asynchronous work is capable of being pushed to another thread but removes any concerns about multithreading.
You can install a nested message loop on the main thread. This will execute the asynchronous code on the calling thread, but also introduces reentrancy, which is extremely difficult to reason about correctly.
In short, there is no one answer. Every single approach is a hack that works for different kinds of asynchronous code.
Simply calling .Result
or .Wait
against your async method will deadlock because you're in the context of a GUI application. See https://msdn.microsoft.com/en-us/magazine/jj991977.aspx (chapter 'Async All the Way') for a nice explanation.
The solution to your problem is not easy, but it has been described in details by Stephen Cleary: here.
So you should use the Nito.AsyncEx library (available on Nuget).
If you really can't add the library he wrote to your project, you could check the source code and use portions of it, the MIT license allows it.
Just add a .Result
call at the end of the method call.
var strCollection = MyAsyncDriverFunction().Result;
I'm not sure what the experts would say, but based on the Stephen Cleary advices I end up with the following idea. Having the following class
public sealed class AsyncTask
{
public static void Run(Func<Task> asyncFunc)
{
var originalContext = SynchronizationContext.Current;
bool restoreContext = false;
try
{
if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
{
restoreContext = true;
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
var task = asyncFunc();
task.GetAwaiter().GetResult();
}
finally
{
if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
}
}
public static TResult Run<TResult>(Func<Task<TResult>> asyncFunc)
{
var originalContext = SynchronizationContext.Current;
bool restoreContext = false;
try
{
if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
{
restoreContext = true;
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
}
var task = asyncFunc();
return task.GetAwaiter().GetResult();
}
finally
{
if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
}
}
}
and use it as follows
public void MySyncFunction()
{
try
{
AsyncTask.Run(() => MyAsyncDriverFunction());
}
catch (Exception exp)
{
}
}
would do what you are asking for without deadlock. The key point is to "hide" the current synchronization context during the asynchronous task execution and force using of the default synchronization context which is known to use thread pool for Post
method. Again, I'm not sure if this is good or bad idea and what side effects it could introduce, but once you asked, I'm just sharing it.
Try change "await AsyncExternalLibraryFunction1()" to "AsyncExternalLibraryFunction1().Wait()" and next to it, and remove
async for function "MyAsyncDriverFunction"