Reputation: 1239
By the time the code calls Task.Result
, it has already been awaited, so does the asynchronous pattern here still hold?
class Program
{
static async Task Main(string[] args)
{
var addNumbersTask = AddNumbers(10, 20);
var result = AwaitForResult(addNumbersTask).Result;
Console.WriteLine(result);
}
static async Task<int> AddNumbers(int a, int b)
{
await Task.Delay(250);
return a + b;
}
static async Task<int> AwaitForResult(Task<int> task)
{
await task;
return task.Result;
}
}
Background if you're interested: Trying to emit IL code for a proxy class that needs to handle async calls, but I don't want to generate the async state machine in the IL. So I figured I could delegate the actual "await" part to a helper outside of the IL. Also, I know there are proxy types out there but the hopeless engineer in me wants to write it myself.
Edit: Updated example.
interface IService
{
Task<int> AddAsync(int a, int b);
}
class Service : IService
{
public async Task<int> AddAsync(int a, int b)
{
await Task.Delay(250); // Some web service call...
return a + b;
}
}
// This class 100% generated via reflection emit
class Proxy : IService
{
private readonly IService _actual;
public Proxy(IService actual) => _actual = actual;
public Task<int> AddAsync(int a, int b)
{
return Awaiter.Await(_actual.AddAsync(a, b));
}
}
static class Awaiter
{
public static async Task<int> Await(Task<int> task)
{
return await task;
}
}
class Program
{
static async Task Main(string[] args)
{
var proxy = new Proxy(new Service());
var result = await proxy.AddAsync(5, 5);
Console.WriteLine($"Result is {result}", result);
}
}
Upvotes: 0
Views: 636
Reputation: 456417
does the asynchronous pattern here still hold?
No. There's nothing magical about using await
on a task. Instead, consider whether the task is completed. Calling task.Result
will block the calling thread until task
completes. Doing await task
will asynchronously wait until task
completes.
So in this code, Result
will not block:
static async Task<int> AwaitForResult(Task<int> task)
{
// Task may not yet be complete here.
await task;
// At this point, task is complete.
// Since task is complete, Result does not block.
return task.Result;
}
But that is totally different than this code:
var result = AwaitForResult(addNumbersTask).Result;
// Equivalent to:
var task = AwaitForResult(addNumbersTask);
var result = task.Result;
The task returned from AwaitForResult
may not be complete here, since it was never await
ed. And if it's not complete, then Result
will block.
Trying to emit IL code for a proxy class that needs to handle async calls, but I don't want to generate the async state machine in the IL. So I figured I could delegate the actual "await" part to a helper outside of the IL.
Have you tried Roslyn APIs? I find them much more convenient than IL emit.
If your proxy is just a pass-through, then you can just return the inner task directly:
// This class 100% generated via reflection emit
class Proxy : IService
{
private readonly IService _actual;
public Proxy(IService actual) => _actual = actual;
public Task<int> AddAsync(int a, int b) => _actual.AddAsync(a, b);
}
But if you want to add much real logic, then I'd recommend using Roslyn to generate the async
state machine for you.
Upvotes: 2
Reputation: 203811
The method Awaiter.Await
is basically pointless. Outside of adding some overhead, the returned task will be functionally identical to the one passed in.
The only actual difference in code between simply returning the result of _actual.AddAsync(a, b)
and awaiting it is that, if AddAsync
throws an exception, if the method is async
it will return a faulted task instead of throwing. So if that either can't happen, or if Proxy.AddAsync
can safely throw itself in that situation, then simply return the value, no need to do anything. If you need to ensure that Proxy.AddAsync
never throws an exception, and instead returns a faulted task if Proxy.AddAsync
throws an excpetion, then you simply need to wrap the method in a try/catch
and return a faulted exception in the catch block. You don't need to replicate any of the rest of the logic of the async
state machine.
So assuming you want the same exception semantics as the behavior of the service's method, which I suspect you do, you can treat these methods exactly the same as you would a synchronous method, that is to say invoke the method with the appropriate parameters and return the result.
Upvotes: 0
Reputation: 8325
If you’re trying to avoid blocking, then no.
In your console app example it makes little difference, but if you did that somewhere where you don’t want blocking - e.g. a UI event handler - .Result
will still block.
By the time you have a value in addNumbersTask
, that task is started. You then immediately pass the task to AwaitForResult
, which immediately begins to await it, at which point an incomplete task that returns an int
is returned back to main()
, and you then call .Result
on it. You are now blocking that thread for most of the 250ms until that task returns its int
result to .Result
in main()
.
The way you have expressed it in the question, “awaited” does not equal “completed”.
Upvotes: 0