Why would One choose SemaphoreSlim Wait over WaitAsync [duplicate] - c#

I'm trying to find out what is the difference between the SemaphoreSlim use of Wait and WaitAsync, used in this kind of context:
private SemaphoreSlim semaphore = new SemaphoreSlim(1);
public async Task<string> Get()
{
// What's the difference between using Wait and WaitAsync here?
this.semaphore.Wait(); // await this.semaphore.WaitAsync()
string result;
try {
result = this.GetStringAsync();
}
finally {
this.semaphore.Release();
}
return result;
}

If you have async method - you want to avoid any blocking calls if possible. SemaphoreSlim.Wait() is a blocking call. So what will happen if you use Wait() and semaphore is not available at the moment? It will block the caller, which is very unexpected thing for async methods:
// this will _block_ despite calling async method and using await
// until semaphore is available
var myTask = Get();
var myString = await Get(); // will block also
If you use WaitAsync - it will not block the caller if semaphore is not available at the moment.
var myTask = Get();
// can continue with other things, even if semaphore is not available
Also you should beware to use regular locking mechanisms together with async\await. After doing this:
result = await this.GetStringAsync();
You may be on another thread after await, which means when you try to release the lock you acquired - it might fail, because you are trying to release it not from the same thread you acquired it. Note this is NOT the case for semaphore, because it does not have thread affinity (unlike other such constructs like Monitor.Enter, ReaderWriterLock and so on).

The difference is that Wait blocks the current thread until semaphore is released, while WaitAsync does not.

Related

Why will the synchronous continuations no longer be executed synchronously?

In the following article, Stephen Toub describes how to build a ManualResetEvent using the TPL. In particular, he states that in the following method:
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(
s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs,
CancellationToken.None,
TaskCreationOptions.PreferFairness,
TaskScheduler.Default);
tcs.Task.Wait();
}
the tcs.Task.Wait() will "block until the task being completed is actually finished (not including the task’s synchronous continuations, just the task itself)". Why won't we block on any synchronous continuations? That is, what will prevent any synchronous continuations on any Tasks awaiting tcs.Task's completion from executing synchronously?
He means that if you do is this way:
public void Set()
{
m_tsc.TrySetResult(true);
}
Then all synchronous continuations of TaskCompletionSource Task will run synchronously on this same thread, blocking Set method for the duration.
However if you do it the way described in your question, then all those continuations will still run, but on thread pool thread, because that's where you execute TrySetResult. As soon as TaskCompletionSource task completes - your tcs.Task.Wait() will return, before synchronous continuations will run. So in effect, Set method will not block waiting for them to complete.
This is because the work in ContinueWith is a different task. All ContinueWith variants say:
Returns
A new continuation Task.
and in the Remarks we can read:
Remarks
The returned Task will not be scheduled for execution until the current task has completed.
BTW. These days ContinueWith is discouraged and await is preferred in almost all cases.
the tcs.Task.Wait() will "block until the task being completed is actually finished (not including the task’s synchronous continuations, just the task itself)". Why won't we block on any synchronous continuations? That is, what will prevent any synchronous continuations on any Tasks awaiting tcs.Task's completion from executing synchronously?
Your question is about Tasks in general, not about the TaskCompletionSource.Task in particular. The answer is that when an incomplete Task is waited, the Wait is performed by a ManualResetEventSlim, which is Set by a task continuation, which is prioritized in front of all other continuations that might be attached previously. Here is a part of the private SpinThenBlockingWait method, from the Task.cs source code:
private bool SpinThenBlockingWait(int millisecondsTimeout,
CancellationToken cancellationToken)
{
bool infiniteWait = millisecondsTimeout == Timeout.Infinite;
uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount;
bool returnValue = SpinWait(millisecondsTimeout);
if (!returnValue)
{
var mres = new SetOnInvokeMres();
try
{
AddCompletionAction(mres, addBeforeOthers: true);
if (infiniteWait)
{
bool notifyWhenUnblocked = ThreadPool.NotifyThreadBlocked();
try
{
returnValue = mres.Wait(Timeout.Infinite, cancellationToken);
//...
This method is invoked internally by the Wait, when the task is not complete at the moment, and the current thread must be blocked. The SetOnInvokeMres is a small class that inherits from the ManualResetEventSlim:
private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction
{
internal SetOnInvokeMres() : base(false, 0) { }
public void Invoke(Task completingTask) { Set(); }
public bool InvokeMayRunArbitraryCode => false;
}
The interesting point is the addBeforeOthers: true argument. This ensures that the ManualResetEventSlim will be signaled before invoking any continuation that is associated with user-supplied code. The Task Parallel Library exposes no public API that allows to attach a continuation with addBeforeOthers: true. Actually this parameter seems to exist exclusively for supporting the signaling of ManualResetEventSlims with priority.

Should IAsyncEnumerator<T>.DisposeAsync() throw if the last MoveNextAsync() task is not awaited?

Why does the UseEventsFail throw in the code below? Could it be that I dispose the async enumerator without awaiting the last MoveNextAsync() task? This example is simplified repro of my real program, so I need to dispose the async enumerator to release its resources. And Task.CompletedTask is usually a Task.Delay() used as a timeout for UseEvents(). If the enumerator task completes before the timeout task, then no exception is thrown.
Stack trace of exception:
at Program.<<<Main>$>g__GenerateEvents|0_3>d.System.IAsyncDisposable.DisposeAsync()
Code:
// All these are ok
await GenerateEvents().GetAsyncEnumerator().DisposeAsync();
await using var enu = GenerateEvents().GetAsyncEnumerator();
await UseEvents();
await UseEvents2();
// This fail
await UseEventsFail();
async Task UseEvents()
{
await using var enu = GenerateEvents().GetAsyncEnumerator();
await Task.WhenAny(enu.MoveNextAsync().AsTask());
}
async Task UseEvents2()
{
var enu = GenerateEvents().GetAsyncEnumerator();
await Task.WhenAny(enu.MoveNextAsync().AsTask(), Task.CompletedTask);
}
async Task UseEventsFail()
{
await using var enu = GenerateEvents().GetAsyncEnumerator();
await Task.WhenAny(enu.MoveNextAsync().AsTask(), Task.CompletedTask);
}
async IAsyncEnumerable<bool> GenerateEvents()
{
while (true) {
await Task.Delay(1000);
yield return true;
}
}
From the MSDN article Iterating with Async Enumerables in C# 8 by Stephen Toub:
It should be evident that it’s fine for one MoveNextAsync call to occur on a different thread from a previous or subsequent MoveNextAsync call; after all, the implementation may await a task and continue execution somewhere else. However, that doesn’t mean MoveNextAsync is “thread-safe”—far from it. On a given async enumerator, MoveNextAsync must never be invoked concurrently, meaning MoveNextAsync shouldn’t be called again on a given enumerator until the previous call to it has completed. Similarly, DisposeAsync on an iterator shouldn’t be invoked while either MoveNextAsync or DisposeAsync on that same enumerator is still in flight.
So, no, IAsyncEnumerator<T>s do not support concurrency. The same is true for IEnumerator<T>s. You can't call Dispose from one thread while a MoveNext is running on another thread. Enumerating in general (synchronously or asynchronously) is not a thread-safe procedure, and must be synchronized.

Task<T> Extension Method for Returning Result

I was wondering, would there be any gotchas from using an extension method for Task<T> like so:
public static T Await<T>(this Task<T> task)
{
var result = default(T);
Task.Run(async () => result = await task).Wait();
return result;
}
It seems like a decent time saver for those instances where you would like to get the result from a Task but you're in a method that is not marked with async.
You code will not work like you want because you are passing in a "Hot Task" to the function.
I assume the reason you are doing this is to prevent the deadlock of just calling task.Result. The reason the deadlock happens is you are blocking the UI thread and the task's captured sync context uses the UI thread for it's postbacks. The problem is the context is captured when the task starts not when you await it.
So if you did on your UI thread
Task<Foo> task = SomeMethodAsync();
Foo result = task.Await();
you are still going to deadlock because the SynchronizationContext that SomeMethodAsync() captured is the UI context, and any internal await inside SomeMethodAsync() that does not use .ConfiguerAwait(false) will try to use the UI thread which will be blocked by your .Wait() call in Await().
The only way to reliably get it to work is if the method took in a Func<Task<T>> instead of just Task<T>, you could then start the task in the background thread to ensure the sync context is not set.
public static T BlockWithoutLockup<T>(Func<Task<T>> task)
{
T result;
if(SynchronizationContext.Current != null)
{
//We use ".GetAwaiter().GetResult()" instead of .Result to get the exception handling
// like we would if we had called `await` or the function directly.
result = Task.Run(task).GetAwaiter().GetResult();
}
else
{
//If we are on the default sync context already just run the code, no need to
// spin up another thread.
result = task().GetAwaiter().GetResult();
}
return result;
}

Migrating lock to TPL

In normal C# we write
int DoSomething(){/*...*/)};
lock(mutex)
{
return DoSomething();
}
to ensure in all cases the mutex is released.
But if the signature of DoSomething changed to
Task<int> DoSomeThingAsync(){/*...*/};
Does the following code
return Task.Factory.StartNew(() =>
{
Monitor.Enter(mutex);
return DoSomethingAsync();
}).Unwrap().ContinueWith(t =>
{
Monitor.Exit(mutex);
return t;
}).Unwrap();
do similar things? Is it guaranteed to release the mutex whenever it was entered? Are there any simpler way to do so? (I am not able to use the async keyword so keep thinking in TPL only)
You can't use Monitor in that way because Monitor is thread-affine and in your case the task and continuation may run on different threads.
The appropriate synchronization mechanism to use is a SemaphoreSlim (which isn't thread-affine) set to 1:
public SemaphoreSlim _semaphore = new SemaphoreSlim(1,1);
_semaphore.Wait();
return DoSomethingAsync().ContinueWith(t =>
{
_semaphore.Release();
return t.Result;
});
As long as you don't use one of the TaskContinuationOptions such as OnlyOnFaulted or OnlyOnCanceled the continuation would always run after the task has completed and so the semaphore is guaranteed to be released.

Calling TaskCompletionSource.SetResult in a non blocking manner

I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.
This is a simplified version that is started in an ordinary Thread
void ReceiverRun()
while (true)
{
var msg = ReadNextMessage();
TaskCompletionSource<Response> task = requests[msg.RequestID];
if(msg.Error == null)
task.SetResult(msg);
else
task.SetException(new Exception(msg.Error));
}
}
The "async" part of the code looks something like this.
await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();
The Wait is actually nested inside non-async calls.
The SendAwaitResponse(simplified)
public static Task<Response> SendAwaitResponse(string msg)
{
var t = new TaskCompletionSource<Response>();
requests.Add(GetID(msg), t);
stream.Write(msg);
return t.Task;
}
My assumption was that the second SendAwaitResponse would execute in a ThreadPool thread but it continues in the thread created for ReceiverRun.
Is there anyway to set the result of a task without continuing its awaited code?
The application is a console application.
I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.
Yes, I have a blog post documenting this (AFAIK it's not documented on MSDN). The deadlock happens because of two things:
There's a mixture of async and blocking code (i.e., an async method is calling Wait).
Task continuations are scheduled using TaskContinuationOptions.ExecuteSynchronously.
I recommend starting with the simplest possible solution: removing the first thing (1). I.e., don't mix async and Wait calls:
await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();
Instead, use await consistently:
await SendAwaitResponse("first message");
await SendAwaitResponse("second message");
If you need to, you can Wait at an alternative point further up the call stack (not in an async method).
That's my most-recommended solution. However, if you want to try removing the second thing (2), you can do a couple of tricks: either wrap the SetResult in a Task.Run to force it onto a separate thread (my AsyncEx library has *WithBackgroundContinuations extension methods that do exactly this), or give your thread an actual context (such as my AsyncContext type) and specify ConfigureAwait(false), which will cause the continuation to ignore the ExecuteSynchronously flag.
But those solutions are much more complex than just separating the async and blocking code.
As a side note, take a look at TPL Dataflow; it sounds like you may find it useful.
As your app is a console app, it runs on the default synchronization context, where the await continuation callback will be called on the same thread the awaiting task has become completed on. If you want to switch threads after await SendAwaitResponse, you can do so with await Task.Yield():
await SendAwaitResponse("first message");
await Task.Yield();
// will be continued on a pool thread
// ...
SendAwaitResponse("second message").Wait(); // so no deadlock
You could further improve this by storing Thread.CurrentThread.ManagedThreadId inside Task.Result and comparing it to the current thread's id after the await. If you're still on the same thread, do await Task.Yield().
While I understand that SendAwaitResponse is a simplified version of your actual code, it's still completely synchronous inside (the way you showed it in your question). Why would you expect any thread switch in there?
Anyway, you probably should redesign your logic the way it doesn't make assumptions about what thread you are currently on. Avoid mixing await and Task.Wait() and make all of your code asynchronous. Usually, it's possible to stick with just one Wait() somewhere on the top level (e.g. inside Main).
[EDITED] Calling task.SetResult(msg) from ReceiverRun actually transfers the control flow to the point where you await on the task - without a thread switch, because of the default synchronization context's behavior. So, your code which does the actual message processing is taking over the ReceiverRun thread. Eventually, SendAwaitResponse("second message").Wait() is called on the same thread, causing the deadlock.
Below is a console app code, modeled after your sample. It uses await Task.Yield() inside ProcessAsync to schedule the continuation on a separate thread, so the control flow returns to ReceiverRun and there's no deadlock.
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
class Program
{
class Worker
{
public struct Response
{
public string message;
public int threadId;
}
CancellationToken _token;
readonly ConcurrentQueue<string> _messages = new ConcurrentQueue<string>();
readonly ConcurrentDictionary<string, TaskCompletionSource<Response>> _requests = new ConcurrentDictionary<string, TaskCompletionSource<Response>>();
public Worker(CancellationToken token)
{
_token = token;
}
string ReadNextMessage()
{
// using Thread.Sleep(100) for test purposes here,
// should be using ManualResetEvent (or similar synchronization primitive),
// depending on how messages arrive
string message;
while (!_messages.TryDequeue(out message))
{
Thread.Sleep(100);
_token.ThrowIfCancellationRequested();
}
return message;
}
public void ReceiverRun()
{
LogThread("Enter ReceiverRun");
while (true)
{
var msg = ReadNextMessage();
LogThread("ReadNextMessage: " + msg);
var tcs = _requests[msg];
tcs.SetResult(new Response { message = msg, threadId = Thread.CurrentThread.ManagedThreadId });
_token.ThrowIfCancellationRequested(); // this is how we terminate the loop
}
}
Task<Response> SendAwaitResponse(string msg)
{
LogThread("SendAwaitResponse: " + msg);
var tcs = new TaskCompletionSource<Response>();
_requests.TryAdd(msg, tcs);
_messages.Enqueue(msg);
return tcs.Task;
}
public async Task ProcessAsync()
{
LogThread("Enter Worker.ProcessAsync");
var task1 = SendAwaitResponse("first message");
await task1;
LogThread("result1: " + task1.Result.message);
// avoid deadlock for task2.Wait() with Task.Yield()
// comment this out and task2.Wait() will dead-lock
if (task1.Result.threadId == Thread.CurrentThread.ManagedThreadId)
await Task.Yield();
var task2 = SendAwaitResponse("second message");
task2.Wait();
LogThread("result2: " + task2.Result.message);
var task3 = SendAwaitResponse("third message");
// still on the same thread as with result 2, no deadlock for task3.Wait()
task3.Wait();
LogThread("result3: " + task3.Result.message);
var task4 = SendAwaitResponse("fourth message");
await task4;
LogThread("result4: " + task4.Result.message);
// avoid deadlock for task5.Wait() with Task.Yield()
// comment this out and task5.Wait() will dead-lock
if (task4.Result.threadId == Thread.CurrentThread.ManagedThreadId)
await Task.Yield();
var task5 = SendAwaitResponse("fifth message");
task5.Wait();
LogThread("result5: " + task5.Result.message);
LogThread("Leave Worker.ProcessAsync");
}
public static void LogThread(string message)
{
Console.WriteLine("{0}, thread: {1}", message, Thread.CurrentThread.ManagedThreadId);
}
}
static void Main(string[] args)
{
Worker.LogThread("Enter Main");
var cts = new CancellationTokenSource(5000); // cancel after 5s
var worker = new Worker(cts.Token);
Task receiver = Task.Run(() => worker.ReceiverRun());
Task main = worker.ProcessAsync();
try
{
Task.WaitAll(main, receiver);
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
Worker.LogThread("Leave Main");
Console.ReadLine();
}
}
}
This is not much different from doing Task.Run(() => task.SetResult(msg)) inside ReceiverRun. The only advantage I can think of is that you have an explicit control over when to switch threads. This way, you can stay on the same thread for as long as possible (e.g., for task2, task3, task4, but you still need another thread switch after task4 to avoid a deadlock on task5.Wait()).
Both solutions would eventually make the thread pool grow, which is bad in terms of performance and scalability.
Now, if we replace task.Wait() with await task everywhere inside ProcessAsync in the above code, we will not have to use await Task.Yield and there still will be no deadlocks. However, the whole chain of await calls after the 1st await task1 inside ProcessAsync will actually be executed on the ReceiverRun thread. As long as we don't block this thread with other Wait()-style calls and don't do a lot of CPU-bound work as we're processing messages, this approach might work OK (asynchronous IO-bound await-style calls still should be OK, and they may actually trigger an implicit thread switch).
That said, I think you'd need a separate thread with a serializing synchronization context installed on it for processing messages (similar to WindowsFormsSynchronizationContext). That's where your asynchronous code containing awaits should run. You'd still need to avoid using Task.Wait on that thread. And if an individual message processing takes a lot of CPU-bound work, you should use Task.Run for such work. For async IO-bound calls, you could stay on the same thread.
You may want to look at ActionDispatcher/ActionDispatcherSynchronizationContext from #StephenCleary's
Nito Asynchronous Library for your asynchronous message processing logic. Hopefully, Stephen jumps in and provides a better answer.
"My assumption was that the second SendAwaitResponse would execute in a ThreadPool thread but it continues in the thread created for ReceiverRun."
It depends entirely on what you do within SendAwaitResponse. Asynchrony and concurrency are not the same thing.
Check out: C# 5 Async/Await - is it *concurrent*?
A little late to the party, but here's my solution which i think is added value.
I've been struggling with this also, i've solved it by capturing the SynchronizationContext on the method that is awaited.
It would look something like:
// just a default sync context
private readonly SynchronizationContext _defaultContext = new SynchronizationContext();
void ReceiverRun()
{
while (true) // <-- i would replace this with a cancellation token
{
var msg = ReadNextMessage();
TaskWithContext<TResult> task = requests[msg.RequestID];
// if it wasn't a winforms/wpf thread, it would be null
// we choose our default context (threadpool)
var context = task.Context ?? _defaultContext;
// execute it on the context which was captured where it was added. So it won't get completed on this thread.
context.Post(state =>
{
if (msg.Error == null)
task.TaskCompletionSource.SetResult(msg);
else
task.TaskCompletionSource.SetException(new Exception(msg.Error));
});
}
}
public static Task<Response> SendAwaitResponse(string msg)
{
// The key is here! Save the current synchronization context.
var t = new TaskWithContext<Response>(SynchronizationContext.Current);
requests.Add(GetID(msg), t);
stream.Write(msg);
return t.TaskCompletionSource.Task;
}
// class to hold a task and context
public class TaskWithContext<TResult>
{
public SynchronizationContext Context { get; }
public TaskCompletionSource<TResult> TaskCompletionSource { get; } = new TaskCompletionSource<Response>();
public TaskWithContext(SynchronizationContext context)
{
Context = context;
}
}

Categories

Resources