-
Notifications
You must be signed in to change notification settings - Fork 1
Async Await Task
- APM = Asynchronous Programming Model = IAsyncResult = Begin / End (not recommended)
- EAP = Event-based Asynchronous Pattern = Async suffix + events, event handler + EventArg (not recommended)
- TAP = Task-based Asynchronous Pattern = async / await (recommended)
TAP method return Task or Task<TResult>
and don't accept out
and ref
parameters. It might support Cancellation / Progression
Task.Run
(since .NET 4.5) = sugar shortcut for Task.Factory.StartNew
Task.Factory.StartNew
oftens return Task<Task>
you will have to unwrap() it..
var t = Task.Factory.StartNew(async ()=>{
await Task.Delay(300);
}); //t is Task<Task<VoidTaskResult>>
var tt = t.Unwrap(); //tt is Task<VoidTaskResult>
tt.Wait(); //t.Wait() will return immediately so we must to wait the unwrap task
- If the work you have is I/O-bound, use
async
andawait
. You should not use the TPL (Task.Run
orParallel.For
..) - If the work you have is CPU-bound and you care about responsiveness, use
async
andawait
but spawn the work off on another thread withTask.Run
You will mostly see I/O-bounds code. The CPU-bounds code is quite rare, it is codes with big for-loop, heavy computational eg. regular expression, linq over object..
Example: I/O-bounds code Load data from 100 databases:
Good code: attack all the database at once with asyn / await
public async Task<List<House>> LoadHousesAsync() {
var tasks = new List<Task<House>>();
foreach (var db in DatabaseList) {
Task<House> t = db.LoadHouseAsync();
tasks.Add(t);
}
House[] resu = await Task.WhenAll(tasks);
return resu.ToList();
}
Bad code: use TPL to spawn new threads which wait for I/O
public async List<House> LoadHousesInParalle() {
var resu = new BlockingCollection<House>();
Parallel.ForEach (DatabaseList, db => {
House house = db.LoadHouse();
resu.Add(house);
});
return resu.ToList();
}
Example 2:
private static Random rand = new Random();
/// let say this is an I/O Bound task
private static async Task doSomeReadWriteAsync(int id) {
var t = rand.Next(1000);
Log.Information($"{id} start {t}");
await Task.Delay(t);
Log.Information($"{id} end");
}
Bad code do read/write once by once
private static async Task ForLoopTpl() {
Log.Information("ForLoopTpl start");
for (int i = 0; i < 3; i++) {
await doSomeReadWriteAsync(i);
}
Log.Information("ForLoopTpl end");
}
/*
ForLoopTpl start
0 start 443
0 end
1 start 906
1 end
2 start 186
2 end
ForLoopTpl end
*/
Good code: read/write all at once in parallel
private static async Task ForLoopTpl() {
Log.Information("ForLoopTpl start");
var t = new List<Task>();
for (int i=0; i<3; i++) {
t.Add(doSomeReadWriteAsync(i));
}
await Task.WhenAll(t); //don't use Task.WaitAll() -> it will block the current thread
Log.Information("ForLoopTpl end");
}
/*
ForLoopTpl start
0 start 238
1 start 419
2 start 29
2 end
0 end
1 end
ForLoopTpl end
*/
-
async void f()
is a void-returning task (vrt) -
async Task f()
is a task-returning task -
A void-returning task (vrt) is a "fire and forget" mechanism
- The caller is unable to know when the vrt will finish:
await vrt
won't wait until thevrt
finish, it only wait until the firstawait
inside thevrt
body. - The caller is unable to catch exceptions inside of the vrt.
- The caller is unable to know when the vrt will finish:
-
Use vrt only for top-level event handlers (or if you have no choices)
-
Whenever you see an async lambda, it could be void-returning or task-returning => you will have to verify it every single time that you see it
Example 1:
Action f1 = async () => { Console.WriteLine("f1 begin"); await Task.Delay(300); Console.WriteLine("f1 end"); };
Func<Task> f2 = async () => { Console.WriteLine("f2 begin"); await Task.Delay(300); Console.WriteLine("f2 end"); };
var t1 = Task.Run(f1); //t1 is Task<void> (t1 is a void-returning task)
t1.Wait(); //it won't wait, but return immediately (Run and Forget)
Console.WriteLine("t1 wait exit");
var t2 = Task.Run(f2); //t2 is Task<VoidTaskResult> (t2 is a task-returning task)
t2.Wait(); //it will wait
Console.WriteLine("t2 wait exit");
Task.Delay(1000).Wait();
result
f1 begin
t1 wait exit
f2 begin
f1 end
f2 end
t2 wait exit
in case overloading ambigues: Task.Run(async()=>{..})
=> it will consider the parameter as task-returning => no worry!
Example 2:
//Task RunAsync(Priority priority, DispatchedHandler h);
//delegate void DispatchedHandler();
try {
await dispatcher.RunAsync(Priority.Normal, async() => {
await LoadAsync();
throw new Exception();
})
}
catch (Exception) {
...
}
There is a async lambda in this code => we must to check if it is "async void" or "async task"..
The async lambda is a DispatchedHandler
which returns void
=> so it is a void-returning task => so the await
and try / catch
in the caller is completly useless. It won't wait nor catching any exception..
1. Avoid fake async codes: All methods suffixed with Async must to be marked with the async
keyword. Example:
Good code
static async Task ComputeAsync() {
..await somewhere
}
=> if the body won't spawn any new thread => the code is truely async
BAD code
static Task ComputeAsync() {
return Task.Run(() => { //spaw a new thread
Compute();
});
}
=> the code look like async, but it is fake, it use up CPU and ThreadPool => it is beter to keep only the sync version of Compute()
and let the callers decide whether they will run it in other thread or not..
VERY BAD code
static Task Compute() {
var t = ComputeAsync();
t.Wait(); //freeze the UI Thread (SynchronizationContext)
}
static async Task ComputeAsync() {
await
return something; //wait for the UI Thread
}
it look like sync codes, but it isn't, and it might cause dead-lock:
- the UI-Thread is freezed because the ComputeAsync is not finished
- the ComputeAsync is not finished because the UI-Thread is freezed
To avoid deadlock:
- In your “library” async methods, use
ConfigureAwait(false)
wherever possible. - Don’t block on Tasks; use async all the way down.
to read: Avoid async void link 1, link 2
https://msdn.microsoft.com/en-us/library/hh873177(v=vs.110).aspx
-
if a method is purely compute-bound, it should be exposed only as a synchronous implementation. Let the user choice if he want to wrap it to another
Task.Run
(orTaskFactory.StartNew
for more fine grained-control). -
All method invole I/O should have an async implementation. the TAP (Task Async Pattern) is implemented
-
with help of the compiler: async await
- compilers perform the necessary transformations to implement the method asynchronously
- An asynchronous method should return either a
System.Threading.Tasks.Task
or aSystem.Threading.Tasks.Task<TResult>
object - In the case of the latter, the body of the function should return a
TResult
, and the compiler ensures that this result is made available through the resulting task object - any exceptions that go unhandled within the body of the method are marshaled to the output task and cause the resulting task to end in the
TaskStatus.Faulted
state - The exception is when an
OperationCanceledException
(or derived type) goes unhandled, in which case the resulting task ends in theTaskStatus.Canceled state
-
or manually with TaskCompletionSource
-
Perform the asynchronous operation, and when it completes, call the
SetResult
,SetException
, orSetCanceled
method, or theTry
version of one of these methods -
Example:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state) { var tcs = new TaskCompletionSource<int>(); stream.BeginRead(buffer, offset, count, ar => { try { tcs.SetResult(stream.EndRead(ar)); } catch (Exception exc) { tcs.SetException(exc); } }, state); return tcs.Task; }
-
-