Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Double await when invoke async lambda via Dispatcher

private async Task<T> LoadForm(WebControlAsync browser, Uri url)
{ ... }

var forms = await await _dispatcher.InvokeAsync(async () => await LoadForm(browser, form.Url));

I don't understand why I have to use two await's here to get T in forms? So it looks as InvokeAsync returns Task<Task<T>>. But when I invoke synchronous method like this:

var forms = await _dispatcher.InvokeAsync(() => FillForm(forms, url));

it requires only single await. So the cause seems to be async lambda. I understand that if I write it this way:

var forms = await await _dispatcher.InvokeAsync(() => LoadForm(browser, form.Url));

then the return type of LoadForm is Task<T> and InvokeAsync returns Task<lambda return type> so it indeed would be Task<Task<T>>. But when a Task method is await'ed, isn't it "unwraps" the actual return type from Task? So if I write:

var forms = await LoadForm(browser, form.Url);

forms would be T, not Task<T>. Why the same isn't happen in async lambda?

like image 664
Aleksey Shubin Avatar asked Jan 10 '23 05:01

Aleksey Shubin


1 Answers

You've answered your own question. InvokeAsync returns a Task<T> where T is the return type of the Func<TResult> delegate supplied to it. When you use an async lambda, you are no longer dealing with Func<TResult>, but instead Func<Task<TResult>>.

The reason you think that unwrapping should happen automatically is probably due to using Task.Run in the past. It should be noted, however, that Task.Run has an overload which accepts a Func<T> and returns a Task<T>, and an overload which accepts a Func<Task<T>>, and still returns Task<T>, so you take this unwrapping which is happening for you behind the covers for granted. This, however, does not apply in your case.

Dispatcher.InvokeAsync is similar to Task.Factory.StartNew in this regard. It does not have an overload which would specifically deal with Func<Task<TResult>>, so you're stuck with unwrapping "manually".

Thing is, think about whether you really need InvokeAsync in your scenario. It won't give you much outside of scenarios where the dispatcher thread is overloaded with work. The Task which it returns will generally complete immediately (without awaiting the Task created by your async lambda) and just give you one more layer of wrapping to deal with. LoadForm is already async so you might as well just use Dispatcher.Invoke which will fire off your async lambda on the correct SynchronizationContext and ultimately return the task that you want to await, or, if you know that your LoadForm will always be called with the right SynchronizationContext (that is, on the UI thread), omit the dispatcher calls altogether and just let async/await do its thing.

like image 51
Kirill Shlenskiy Avatar answered Feb 01 '23 14:02

Kirill Shlenskiy