Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
2.1k views
in Technique[技术] by (71.8m points)

c# - Calling async methods from non-async code

I'm in the process of updating a library that has an API surface that was built in .NET 3.5. As a result, all methods are synchronous. I can't change the API (i.e., convert return values to Task) because that would require that all callers change. So I'm left with how to best call async methods in a synchronous way. This is in the context of ASP.NET 4, ASP.NET Core, and .NET/.NET Core console applications.

I may not have been clear enough - the situation is that I have existing code that is not async aware, and I want to use new libraries such as System.Net.Http and the AWS SDK that support only async methods. So I need to bridge the gap, and be able to have code that can be called synchronously but then can call async methods elsewhere.

I've done a lot of reading, and there are a number of times this has been asked and answered.

Calling async method from non async method

Synchronously waiting for an async operation, and why does Wait() freeze the program here

Calling an async method from a synchronous method

How would I run an async Task<T> method synchronously?

Calling async method synchronously

How to call asynchronous method from synchronous method in C#?

The problem is that most of the answers are different! The most common approach I've seen is use .Result, but this can deadlock. I've tried all the following, and they work, but I'm not sure which is the best approach to avoid deadlocks, have good performance, and plays nicely with the runtime (in terms of honoring task schedulers, task creation options, etc). Is there a definitive answer? What is the best approach?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

So I'm left with how to best call async methods in a synchronous way.

First, this is an OK thing to do. I'm stating this because it is common on Stack Overflow to point this out as a deed of the devil as a blanket statement without regard for the concrete case.

It is not required to be async all the way for correctness. Blocking on something async to make it sync has a performance cost that might matter or might be totally irrelevant. It depends on the concrete case.

Deadlocks come from two threads trying to enter the same single-threaded synchronization context at the same time. Any technique that avoids this reliably avoids deadlocks caused by blocking.

Here, all your calls to .ConfigureAwait(false) are pointless because you are not awaiting.

RunSynchronously is invalid to use because not all tasks can be processed that way.

.GetAwaiter().GetResult() is different from Result/Wait() in that it mimics the await exception propagation behavior. You need to decide if you want that or not. (So research what that behavior is; no need to repeat it here.)

Besides that, all these approaches have similar performance. They will allocate an OS event one way or another and block on it. That's the expensive part. I don't know which approach is absolutely cheapest.

I personally like the Task.Run(() => DoSomethingAsync()).Wait(); pattern because it avoids deadlocks categorically, is simple and does not hide some exceptions that GetResult() might hide. But you can use GetResult() as well with this.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...