Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safety on async/await with memory caching

I was having a look at the section on memory barriers as described in http://www.albahari.com/threading/part4.aspx and attempted to make an async/await version of the example provided under 'Do We Really Need Locks and Barriers?':

public class Program
{
    static void Main(string[] args)
    {
        TestAsync();
        Console.ReadKey(true);
    }

    private static async void TestAsync()
    {
        bool complete = false;
        Func<Task> testFunc = async () =>
        {
            await Task.Delay(1000);
            bool toggle = false;
            while (!complete) toggle = !toggle;
        };

        var task = testFunc();
        Thread.Sleep(2000);
        complete = true;
        await task;
        Console.WriteLine("Done");
    }
}

When run under release mode without debugging, the program will never finish as per the original threading example it's based off.

However, I was under the impression with async/await because of the way the context is saved it would prevent these kinds of issues. Or do all thread safety rules still apply when using async/await?

like image 915
aprofessionalpirate Avatar asked Jan 30 '17 02:01

aprofessionalpirate


People also ask

Is async await thread safe?

Is async await concurrent is a good question ie should I be worried about thread safety writing back to the downloadTasks? In this case no. Interestingly we can use Lists , Queues etc.. as behind the scenes it is all thread safe (the same thread!)

Is IMemoryCache thread safe?

The Core 2.2 IMemoryCache is in theory thread safe. But if you call GetOrCreateAsync from multiple threads the factory Func will be called multiple times. Which could be a bad thing. A very simple fix to this is using a semaphore.

Does async await run on separate thread?

An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created.

Does await block the thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.


2 Answers

This is actually a compile optimization issue. When you compile in release for some reason it predicts complete will never be true and infinitely runs your application. Since you've based this on another example I'm guessing you already knew that. But as far as the async / await it can't be blamed.

To make this work you would still need to set complete as a volatile variable like so:

        static volatile bool complete = false;

This will tell the compiler to check it every cycle regardless and it will work.

I'm not saying I agree with it but what's happening is the compiler is seeing complete unchanged all the way up to the point of the while(!complete) section and since there is no volatile keyword it decides it's never going to change to optimize performance.

Another way to make this work is to remove the compiler optimizations. You can click on the project Properties and then the Build tab and uncheck 'Optimize code'. Then it will work in release.

like image 171
Michael Puckett II Avatar answered Sep 19 '22 20:09

Michael Puckett II


The problem is that access to the complete variable is not synchronized. await will insert barriers before and after the task runs, to make sure that the task continuation sees new values, but notice that in this example that won't help. After complete = true, the fact that the task is awaited does not insert a barrier, and anyway, the looping thread is not synchronizing on the variable read either.

The compiler is allowed to optimize the code, but, even if not, the code might also run forever in practice, as the value of the complete variable is never explicitly made visible between the two threads.

This can be solved by synchronizing methods, like 'volatile'.

like image 38
Ernesto Avatar answered Sep 20 '22 20:09

Ernesto