-2

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)
Daniel Kelley
  • 7,579
  • 6
  • 42
  • 50
Dev Dev
  • 135
  • 2
  • 8
  • 2
    Why *can't* you change `MySyncFunction` to async? Why not return `async Task` ? The only reason would be if it was passed as a callback function to an external library and even this can be circumvented. – Panagiotis Kanavos Oct 12 '15 at 13:19
  • As for the straightforward answer - just call `.Result`. `async` doesn't make a method asynchronous, it only makes it easier to await methods that already return a Task – Panagiotis Kanavos Oct 12 '15 at 13:20
  • 1
    So you `MyAsyncDriverFunction()` returns a Task. You can call `.Wait()` or `.Result` on Task which will block the thread until the completion of the `MyAsyncDriverFunction()` – Callum Linington Oct 12 '15 at 13:20
  • @PanagiotisKanavos That'll freeze the UI for the duration of the opeartion. – Servy Oct 12 '15 at 13:23
  • The problem started from being unable change the MySyncFunction to async, as it is an already established function in my application (of course not the same name or simple functionality) – Dev Dev Oct 12 '15 at 13:24
  • 3
    @DevDev So you *can* make it asynchronous, you just can't be bothered to because it wouldn't be as easy as you'd like? In that case, the solution is simple, make the method asynchronous anyway. Trying to do the work synchronously is going to cause you way more grief in the long term. – Servy Oct 12 '15 at 13:25
  • @DevDev then I suggest you refactor it, or wrap it. You do realize that if this method is called by the UI thread, the UI thread *will* block. – Panagiotis Kanavos Oct 12 '15 at 13:26
  • Ok, this is the point of my question? I don't want UI deadlock. and my hands are tied in terms of changing it to 'async' @PanagiotisKanavos what do you mean by wraping it? what do you suggest – Dev Dev Oct 12 '15 at 13:31
  • 1
    @DevDev The way to not freeze your UI is to *do the work asynchronously*. That is true *by definition*. You've also just said that your hands *aren't* tied in being unable to make it asynchronous, it's just that you'd need to change a lot of downstream code. You need to make it asynchronous. – Servy Oct 12 '15 at 13:33
  • @Servy I keep saying it clear, my hands are tied in terms of changing it to 'async'.... I really can't change it to async? – Dev Dev Oct 12 '15 at 13:35
  • 2
    @DevDev Then you really can't have your application function properly. – Servy Oct 12 '15 at 13:36

5 Answers5

5

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • @ken2k and Stephen Cleary. Are you guys saying if I use Stephen's Nito.AsyncEx Library I'll be able to do what I need safely? – Dev Dev Oct 12 '15 at 13:45
  • @DevDev: No. I'm saying that *there is no solution that works for all possible asynchronous code*. So, your question as it currently stands cannot have an answer. You *have* to know the internals of `MyAsyncDriverFunction` before you can determine which hack(s) are "safe" to use with it. And even then, it'll be a hack, not a proper solution. – Stephen Cleary Oct 12 '15 at 13:47
  • the contents of the MyAsyncDriverFunction have some calls to an async external third party library. The third party library make some REST calls. I hope that answer's your question, or make's a bit clear of what I'm doing – Dev Dev Oct 12 '15 at 13:57
  • @DevDev: No. The kinds of internals I mean are questions like "does the API need to schedule work to the current context in order to complete?", "can the API be called on a different thread?", and "does the API assume its continuations will execute one at a time?", where "API" is defined as the transitive closure of the asynchronous method and all methods it calls. Furthermore, you as the consumer must make the judgement call of "will all these answers still be true in the future?" – Stephen Cleary Oct 12 '15 at 14:36
  • 1
    In case it's not clear, answering these questions is extremely difficult. I've never seen them sufficiently documented, so you'll probably have to look at the source. And that makes the final judgment call ("will this be the same in the future?") quite difficult. – Stephen Cleary Oct 12 '15 at 14:38
1

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.

Community
  • 1
  • 1
ken2k
  • 48,145
  • 10
  • 116
  • 176
0

Just add a .Result call at the end of the method call.

var strCollection = MyAsyncDriverFunction().Result;
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
krillgar
  • 12,596
  • 6
  • 50
  • 86
  • 4
    This is gonna deadlock. – ken2k Oct 12 '15 at 13:23
  • @ken2k The OP actually asked for this. The OP may not realize that "call asynchronouslly" means the UI will block waiting for the method – Panagiotis Kanavos Oct 12 '15 at 13:30
  • 4
    @PanagiotisKanavos This won't just block, this will _deadlock_. Basically your program will be stuck forever, no just until the task completion. – ken2k Oct 12 '15 at 13:31
  • @ken2k possibly - I never write such code (or haven't for 5 years anyway) – Panagiotis Kanavos Oct 12 '15 at 13:33
  • 1
    @PanagiotisKanavos C# hasn't even had the `await` feature for 5 years. It came out in like 2012. – Servy Oct 12 '15 at 13:35
  • 1
    @Servy It had Tasks, and I won't even mention the BCL Async package, it's only 3 years old. I learned pretty quickly to avoid mixing blocking and asynchronous code. It wasn't `async` but a `TaskScheduler.FromCurrentContext` that resulted in the same deadlock – Panagiotis Kanavos Oct 12 '15 at 14:04
0

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.

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • have you used this code before in your application. what do you think @Stephen Cleary? – Dev Dev Oct 12 '15 at 15:54
  • I would like to hear that too. No, I haven't used that before. Before posting the answer, I've did some simple WF test with and w/o using that class (which deadlocked) which seemed to work, but as Stephen said, it could really depend of what the async function is doing. – Ivan Stoev Oct 12 '15 at 16:34
-4

Try change "await AsyncExternalLibraryFunction1()" to "AsyncExternalLibraryFunction1().Wait()" and next to it, and remove async for function "MyAsyncDriverFunction"

denza
  • 66
  • 3