问题
I couldn't find a clear answer about whether returning from an async
method always produces release semantics and whether await
always produces acquire semantics. I assume yes, because otherwise any async/await
code would be a minefield?
So here's an example: are returned values both guaranteed to be 100*2
and 12345*2
, without any explicit locks or barriers?
private static async Task<(int, int)> AMethod()
{
// Runs on the original thread:
var x = 100;
var y = 12345;
var task = Task.Run(() =>
{
// Can run on another thread:
x *= 2;
y *= 2;
// Implicit return here, marking the task completed.
// Release semantics or not?
});
await task; // Acquire semantics or not?
// Runs on the original thread:
return (x, y);
}
EDIT: Of course, Task.Run
also needs to produce a release and an acquire is needed when starting to run the task's code. Forgot about those in the original question.
回答1:
Yes, the returned values are both guaranteed to be 100*2
and 12345*2
, without any explicit locks or barriers.
It's the Task.Run
, not the await
, which produces the memory barrier in this case.
To quote the wonderful Albahari Threading in C#:
The following implicitly generate full fences:
- C#'s lock statement (Monitor.Enter/Monitor.Exit)
- All methods on the Interlocked class (we’ll cover these soon)
- Asynchronous callbacks that use the thread pool — these include asynchronous delegates, APM callbacks, and Task continuations
- Setting and waiting on a signaling construct
- Anything that relies on signaling, such as starting or waiting on a Task
By virtue of that last point, the following is thread-safe:
int x = 0; Task t = Task.Factory.StartNew (() => x++); t.Wait(); Console.WriteLine (x); // 1
Task.Run
wraps ThreadPool.UnsafeQueueUserWorkItem
, which falls under "Asynchronous callbacks that use the thread pool".
See Memory barrier generators for a more comprensive list of things that create memory barriers.
来源:https://stackoverflow.com/questions/55138674/do-async-and-await-produce-acquire-and-release-semantics