Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async tasks and locks

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:

  1. Since async tasks don't run on separate threads, it seems I don't need any kind of lock when updating this list, right?

  2. On the other hand, can we assume that async tasks will never run on separate threads?

  3. 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.

  4. 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?

like image 666
sapito Avatar asked Jan 02 '15 05:01

sapito


People also ask

What does a async lock do?

The lock statement was introduced in c# 2.0 to prevent multi threads access to an object at the same time. In async programming model the goal of the locks is to limit the number of concurrent execution of a block of code to a defined number.

Can we use lock in async await?

Async locks Since the code before and after an await can execute on different threads (in the general case), thread-affine locks can no longer be used.

Can you wrap asynchronous code in a lock statement?

The lock keyword can only be used to synchronize synchronous code. From MSDN: An await expression cannot occur in the body of a synchronous function, in a query expression, in the block of a lock statement, or in an unsafe context.

How can we avoid deadlock in async await?

The solution is simple, use async all the way down. Never block on tasks yourself. Another solution is to call “ConfigureAwait(false)” on the task of the underlying method, to prevent the continuation of the task on the original context captured. If you really cannot use async all the way, then you could use “Task.


2 Answers

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.

like image 160
Stephen Cleary Avatar answered Sep 22 '22 11:09

Stephen Cleary


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.

like image 31
Yuval Itzchakov Avatar answered Sep 21 '22 11:09

Yuval Itzchakov