Why does LogicalCallContext not work with async?

后端 未结 2 1717
一生所求
一生所求 2021-01-31 09:32

In this question the accepted answer by Stephen Cleary says that LogicalCallContext can\'t work correctly with async. He also posted about it in this MSDN thread.

Logica

相关标签:
2条回答
  • 2021-01-31 10:01

    Update: This answer is not correct for .NET 4.5. See my blog post on AsyncLocal for details.

    Here's the situation (repeating several points in your question):

    • LogicalCallContext will flow with async calls; you can use it to set some implicit data and read it from an async method further down your call stack.
    • All copies of LogicalCallContext are shallow copies, without any way for end-user code to hook into a deep-copy kind of operation.
    • When you do "simple parallelism" with async, there is only one copy of the LogicalCallContext shared between the various async methods.

    LogicalCallContext does work fine if your async code is all linear:

    async Task ParentAsync()
    {
      ... = 0; // Set LogicalCallContext value to "0".
    
      await ChildAAsync();
      // LogicalCallContext value here is always "0".
    
      await ChildBAsync();
      // LogicalCallContext value here is always "0".
    }
    
    async Task ChildAAsync()
    {
      int value = ...; // Save LogicalCallContext value (always "0").
      ... = 1; // Set LogicalCallContext value to "1".
    
      await Task.Delay(1000);
      // LogicalCallContext value here is always "1".
    
      ... = value; // Restore original LogicalCallContext value (always "0").
    }
    
    async Task ChildBAsync()
    {
      int value = ...; // Save LogicalCallContext value (always "0").
      ... = 2; // Set LogicalCallContext value to "2".
    
      await Task.Delay(1000);
      // LogicalCallContext value here is always "2".
    
      ... = value; // Restore original LogicalCallContext value (always "0").
    }
    

    But things aren't so nice once you use what I call "simple parallelism" (starting several async methods and then using Task.WaitAll or similar). This is an example similar to my MSDN forum post (for simplicity, assume a non-parallel SynchronizationContext such as GUI or ASP.NET):

    Edit: the code comments are incorrect; see the comments on this question and answer

    async Task ParentAsync()
    {
      ... = 0; // Set LogicalCallContext value to "0".
    
      Task childTaskA = ChildAAsync();
      // LogicalCallContext value here is always "1".
    
      Task childTaskB = ChildBAsync();
      // LogicalCallContext value here is always "2".
    
      await Task.WhenAll(childTaskA, childTaskB);
      // LogicalCallContext value here may be "0" or "1".
    }
    
    async Task ChildAAsync()
    {
      int value = ...; // Save LogicalCallContext value (always "0").
      ... = 1; // Set LogicalCallContext value to "1".
    
      await Task.Delay(1000);
      // LogicalCallContext value here may be "1" or "2".
    
      ... = value; // Restore original LogicalCallContext value (always "0").
    }
    
    async Task ChildBAsync()
    {
      int value = ...; // Save LogicalCallContext value (always "1").
      ... = 2; // Set LogicalCallContext value to "2".
    
      await Task.Delay(1000);
      // LogicalCallContext value here may be "0" or "2".
    
      ... = value; // Restore original LogicalCallContext value (always "1").
    }
    

    The problem is that the LogicalCallContext is shared between ParentAsync, ChildAAsync, and ChildBAsync, without any way to hook into or force a deep-copy operation. In the "linear" example, the context is also shared, but only one method was active at a time.

    Even if the data you store in LogicalCallContext is immutable (as in my integer example), you'd still have to update the LogicalCallContext value in order to implement an NDC, and this means the sharing-without-copies problem is going to mess it up.

    I have looked into this in detail, and have concluded that a solution is not possible. If you can figure one out, I'd be very happy to be proven wrong. :)

    P.S. Stephen Toub pointed out that the recommendation to use CallContext only for remoting (which was given without reason, IIRC) no longer applies. We may feel free to use LogicalCallContext... if we can get it to work. ;)

    0 讨论(0)
  • 2021-01-31 10:20

    Stephen confirms that this works on .Net 4.5 and Win8/2012. Not tested on other platforms, and known not to work on at least some of them. So the answer is that Microsoft got their game together and fixed the underlying issue in at least the most recent version of .Net and the async compiler.

    So the answer is, it does work, just not on older .Net versions. (So the log4net project can't use it to provide a generic NDC.)

    0 讨论(0)
提交回复
热议问题