Reputation: 1522
I have a list of elements that should be updated by two processes. First one is the UI thread (controlled by the user), second one is a background process that retrieves information from a web service.
Since this second process is I/O bound, it seems suitable for async tasks. This leads me to a few questions:
Since async tasks don't run on separate threads, it seems I don't need any kind of lock when updating this list, right?
On the other hand, can we assume that async tasks will never run on separate threads?
I'm talking about a Windows Forms application. Maybe in the future I want it to run it as a console application. AFAIK, in console applications Async tasks run on separate threads. What's the preferred idiom to ask a task if it's running on a separate thread? This way I can establish a lock when necessary.
The fact that I don't know if I really need a lock makes me wonder wether this is the best design or not. Would it make sense to stick to Task.Run()
even for this kind of IO bound code?
Upvotes: 10
Views: 17717
Reputation: 149518
Since async tasks don't run on separate threads, it seems I don't need any kind of lock when updating this list, right?
There is no guarantee that one isn't using Task.Run
and marking his method async
. IO bound async tasks are most likely not using some sort of thread behind the scenes, but that isn't always the case. You shouldn't rely on that for the correctness of your code. You could ensure that your code runs on the UI thread by wrapping it with another async
method which doesn't use ConfigureAwait(false)
. You can always use the concurrent collections given in the framework. Perhaps a ConcurrentBag
or a BlockingCollection
can suite you needs.
AFAIK, in console applications Async tasks run on separate threads. What's the preferred idiom to ask a task if it's running on a separate thread?
That is incorrect. async
operations by themselfs dont run on seperate threads only because they're in a console app. Simply put, the default TaskScheduler
in a console app is the default ThreadPoolTaskScheduler
, which will queue any continuation on a threadpool thread, as a console has no such entity called a ui thread. Generally, it's all about SynchronizationContext
The fact that I don't know if I really need a lock makes me wonder wether this is the best design or not. Would it make sense to stick to Task.Run() even for this kind of IO bound code?
Definitely not. The fact that you don't know is the reason you posted this question, and the reason we're trying to help.
There is no need to use a threadpool thread to be doing async IO. The whole point of async in IO is the fact that you can free the calling thread doing IO to process more work while the request is being processed.
Upvotes: 10
Reputation: 456322
Since async tasks don't run on separate threads, it seems I don't need any kind of lock when updating this list, right?
True enough. This approach works well if you follow a functional pattern (i.e., each background operation will return its result, rather than update shared data). So, something like this will work fine:
async Task BackgroundWorkAsync() // Called from UI thread
{
while (moreToProcess)
{
var item = await GetItemAsync();
Items.Add(item);
}
}
In this case, it doesn't matter how GetItemAsync
is implemented. It can use Task.Run
or ConfigureAwait(false)
all it wants - BackgroundWorkAsync
will always sync up with the UI thread before adding the item to the collection.
Maybe in the future I want it to run it as a console application. AFAIK, in console applications Async tasks run on separate threads.
"Async tasks" don't run at all. If this is confusing, I have an async intro that may be helpful.
Every asynchronous method starts out being executed synchronously. When it hits an await
, it (by default) captures the current context and later uses that to resume executing the method. So, what happens when it's called from a UI thread is that the async
method resumes on the captured UI context. Console apps do not provide a context, so the async
method resumes on a thread pool thread.
What's the preferred idiom to ask a task if it's running on a separate thread? This way I can establish a lock when necessary.
I'd recommend a design that doesn't ask such threading questions. First off, you could just use a plain lock
- they're extremely fast when there's no contention:
async Task BackgroundWorkAsync() // Called from any thread
{
while (moreToProcess)
{
var item = await GetItemAsync();
lock (_mutex)
Items.Add(item);
}
}
Alternatively, you could document that the component depends on a one-at-a-time context provided, and use something like AsyncContext
from my AsyncEx library for the Console app.
Upvotes: 6
Reputation: 13495
Asyc-await
captures the synchronization context before the await
statement and then by default runs the continuation on the same context after the await
statement. Synchronization context for UI thread are only associated with one thread, so in that sort of scenario you are you could end up always updating the list from the UI thread.
But if someone changes the code to call ConfigureAwait(false)
after on of the awaits the continuation will not run on the original synchronization context and you can end up updating the list on one of the thread pool threads.
Also note that you cannot use await
inside a lock
statement but you can use a SemapahoreSlim
to do an asynchronous wait instead.
IMHO it's much better to just use a synchronized collection rather than relying on the list being updated from the same thread.
You cannot assume that, the current synchronization context will be captured but continuations may not always run on it.
I would use a synchronized collection or SempahoreSlim
in this case. For console app the thread pool synchronizationo context is used and continuations could end up running on any of the thread pool threads.
It makes sense to use async-await for IO bound code as it doesn't consume a thread.
I'd stick with using async-await
and change to use a thread safe collection or synchronize using SemaphoreSlim
Upvotes: 2