Search code examples
c#multithreadingasynchronoustask

How can I "lock" the value of a static property inside a Task to what it was when a Task starts?


I would like to start a long-running Task which will use the value of a static property on a static class. I would like it to "capture" the value when it starts the task, and continue to use that value even if/when that static property changes "outside" the task.

MyStaticClass.MyStaticProperty = "foo";
Task.Run(() => 
{
   // Assume this runs for 10 minutes...
   // MyStaticClass.MyStaticProperty should continue to be "foo"
   // because that's what it was when the task started
});


MyStaticClass.MyStaticProperty = "bar";
// Even though this executes milliseconds after the task starts,
// the value of this property inside the task should remain "foo"

Effectively, I want my Task to "copy" the state of this value and keep it's own version of it, isolated from what's happened "outside" the Task while it runs.

Update: I can't just make a local copy of the value, because code in the Task might call other code, which calls other code, etc. And the code that is called will have no knowledge of the local copy of the variable. Code that is X levels removed, will just be ignorantly calling MyStaticClass.MyStaticProperty


Solution

  • This sounds like a solution for AsyncLocal<T>. The example on the docs explain it well.

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    class Example
    {
        static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();
    
        static async Task AsyncMethodA()
        {
            // Start multiple async method calls, with different AsyncLocal values.
            _asyncLocalString.Value = "Value 1";
            var t1 = AsyncMethodB("Value 1");
    
            _asyncLocalString.Value = "Value 2";
            var t2 = AsyncMethodB("Value 2");
    
            // Await both calls
            await t1;
            await t2;
         }
    
        static async Task AsyncMethodB(string expectedValue)
        {
            Console.WriteLine("Entering AsyncMethodB.");
            Console.WriteLine("   Expected '{0}', AsyncLocal value is '{1}'", 
                              expectedValue, _asyncLocalString.Value);
            await Task.Delay(100);
            Console.WriteLine("Exiting AsyncMethodB.");
            Console.WriteLine("   Expected '{0}', got '{1}'", 
                              expectedValue, _asyncLocalString.Value);
        }
    
        static async Task Main(string[] args)
        {
            await AsyncMethodA();
        }
    }
    // The example displays the following output:
    //   Entering AsyncMethodB.
    //      Expected 'Value 1', AsyncLocal value is 'Value 1'
    //   Entering AsyncMethodB.
    //      Expected 'Value 2', AsyncLocal value is 'Value 2'
    //   Exiting AsyncMethodB.
    //      Expected 'Value 2', got 'Value 2'
    //   Exiting AsyncMethodB.
    //      Expected 'Value 1', got 'Value 1'