Does Task.RunSynchronously() work “recursively”?

2019-07-20 16:08发布

问题:

If I call RunSynchronously() on a Task in C#, will this lead to asynchronous calls further down the rabbit hole to be run synchronously as well?

Let's say I have a method named UpdateAsync(). Inside this method another asynchronous call is made to DoSomethingAsync() and inside this again we find DoSomethingElseAsync(), will calling RunSynchronously() on 'UpdateAsync()' lead to RunSynchronously() also indirectly being called on DoSomethingAsync()?

The reason for my question: I have a situation where I "need" to call an asynchronous method (UpdateAsync()) inside a catch-block and wonder if calling this with RunSynchronously() is safe. The documentation is quite clear on the fact that you can't await inside a catch-block. (Strictly speaking I could use a boolean inside the catch-block and call UpdateAsync() after the try-catch, but that feels rather dirty). Sorry about the dual question, but as you probably understand I don't quite know how to phrase it and do not have a really good understanding of this field.

(Edit: I don't know how to find out if a method was called asynchronously. How would you write a unit test for this? Is it possible to log it somehow?)

回答1:

I "need" to call an asynchronous method and wonder if calling this with RunSynchronously() is safe

There are two kinds of Tasks: code-based* Tasks (you can create those by using the Task constructor or Task.Factory.StartNew()) and promise-style Tasks (you can create them manually by using TaskCompletionSource or by writing an async method).

And the only Tasks that you can start by calling Start() or RunSynchronously() are unstarted code-based Tasks. Since async methods return promise-style Tasks (or possibly already started code-based Tasks), calling RunSynchronously() on them is not valid and will result in an exception.

So, to actually answer your question: what you're asking isn't possible, so it doesn't make sense to ask whether it's safe.


* This is not an official name, I don't know if there is one.



回答2:

It's hard to predict without code how it will execute nested async methods.

You can log on each async method Thread.CurrentThread.ManagedThreadId property and compare id with other thread IDs that you have. When they vary, then your async methods are in multithreaded executtion

Or try to use Concurrency Visualizer from Visual Studio, Analyze menu. With Task class instances and even with C#5 async syntax there is no way to get to know that you are executing in parallel or another thread.



回答3:

I think I'll be contradicting @svick's answer, but I feel the OP question is valid, because an async method's "promised" (to use Svick's terminology) Task can be obtained, but not started, thus allowing to do Task.RunSynchronously().

    static void Main(string[] args)
    {
        ...
        //obtain a Task that's not started
        var t = new Task<int>((ob) => GetIntAsync((string)ob).Result, someString);
        ...
    }

    static async Task<int> GetIntAsync(string callerThreadId)
    {
       ...

Having said that, the answer is: No, the RunSynchronously() affects the task you run this on. If the call chain later contains more async calls, they run asynchronously.

I have a little console app that is modeling this, is someone is interested in seeing it, but the concept is pretty simple to reproduce - just chain enough asynchronous calls and toggle between running the earliest one synchronously and asynchronously to see the different behavior.