为什么LogicalCallContext无法与异步工作?(Why does LogicalCall

2019-08-05 17:49发布

在这个问题由斯蒂芬·克利里公认的答案说,LogicalCallContext无法与异步正常工作。 他还发布了关于它在这个 MSDN线程。

LogicalCallContext保持一个Hashtable存储发送到CallContext.LogicalGet / SetData的数据。 它不仅会哈希表的浅表副本。 所以,如果你存储一个可变对象在里面,不同的任务/线程将看到对方的变化。 这就是为什么斯蒂芬·克利里的例子NDC计划(发布于该MSDN线程)无法正常工作。

但是AFAICS,如果你只存储在Hashtable不可改变的数据(比如用不可变的集合 ),应该干活,而让我们实现一个NDC。

然而,斯蒂芬·克利里也该接受的回答说:

CallContext中不能被用于此目的。 微软已经明确建议反对使用CallContext中的任何东西,除了远程处理。 更重要的是,逻辑CallContext中不懂得如何异步方法提前返回,并在稍后继续。

不幸的是,链接到微软的建议是向下(网页未找到)。 所以,这是为什么不建议我的问题? 为什么我不能以这种方式使用LogicalCallContext? 这是什么意思就是说它不理解异步方法? 从来电者的POV他们只是方法返回的任务,不是吗?

ETA:又见这个问题等 。 在那里,由斯蒂芬·克利里回答说:

你可以使用CallContext.LogicalSetData和CallContext.LogicalGetData,但我建议你不这样做,因为他们不支持任何形式的“克隆”,当您使用简单的并行

这似乎支持我的情况。 所以,我应该能够建立一个NDC,这实际上是我需要的,只是不适合log4net的。

我写了一些示例代码和它似乎工作,但仅仅是测试并不总是赶并发错误。 所以,因为有这些其他职位的提示,这可能无法正常工作,我仍然在问:是这种方法有效吗?

ETA:当我从下面的答案)运行斯蒂芬提出的摄制,我没有得到错误的答案,他说我会,我得到正确的答案。 即使他说,“这里LogicalCallContext值始终为‘1’”,我总是得到0的正确值是这可能是由于竞争状态? 无论如何,我仍未重现我自己的电脑上的任何实际问题。 下面是我运行的确切代码; 它打印唯一的“真”在这里,在这里斯蒂芬说,它应打印“假”至少一些的时间。

private static string key2 = "key2";
private static int Storage2 { 
    get { return (int) CallContext.LogicalGetData(key2); } 
    set { CallContext.LogicalSetData(key2, value);} 
}

private static async Task ParentAsync() {
  //Storage = new Stored(0); // Set LogicalCallContext value to "0".
  Storage2 = 0;

  Task childTaskA = ChildAAsync();
  // LogicalCallContext value here is always "1".
  // -- No, I get 0
  Console.WriteLine(Storage2 == 0);

  Task childTaskB = ChildBAsync();
  // LogicalCallContext value here is always "2".
  // -- No, I get 0
  Console.WriteLine(Storage2 == 0);

  await Task.WhenAll(childTaskA, childTaskB);
  // LogicalCallContext value here may be "0" or "1".
  // -- I always get 0
  Console.WriteLine(Storage2 == 0);
}

private static async Task ChildAAsync() {
  var value = Storage2; // Save LogicalCallContext value (always "0").
  Storage2 = 1; // Set LogicalCallContext value to "1".

  await Task.Delay(1000);
  // LogicalCallContext value here may be "1" or "2".
  Console.WriteLine(Storage2 == 1);

  Storage2 = value; // Restore original LogicalCallContext value (always "0").
}

private static async Task ChildBAsync() {
  var value = Storage2; // Save LogicalCallContext value (always "1").
  Storage2 = 2; // Set LogicalCallContext value to "2".

  await Task.Delay(1000);
  // LogicalCallContext value here may be "0" or "2".
  Console.WriteLine(Storage2 == 2);

  Storage2 = value; // Restore original LogicalCallContext value (always "1").
}

public static void Main(string[] args) {
  try {
    ParentAsync().Wait();
  }
  catch (Exception e) {
    Console.WriteLine(e);
  }

所以我重申问题是,什么(如果有的话)是错的,上面的代码?

此外,当我在看的CallContext.LogicalSetData代码,它调用Thread.CurrentThread.GetMutableExecutionContext()和修改了。 而GetMutableExecutionContext说:

if (!this.ExecutionContextBelongsToCurrentScope)
    this.m_ExecutionContext = this.m_ExecutionContext.CreateMutableCopy();
  this.ExecutionContextBelongsToCurrentScope = true;

而CreateMutableCopy最终是否LogicalCallContext的哈希表保存用户提供的数据的浅表副本。

所以,试图理解为什么这个代码不斯蒂芬的工作,是不是因为ExecutionContextBelongsToCurrentScope有错误的价值有时会? 如果是这样的话,也许我们可以注意到,当它 - 通过看,要么当前任务ID或当前线程ID已经改变 - 和手动存储在我们不变的结构,通过螺纹+任务ID键入不同的值。 (有使用这种方法的性能问题,例如死任务数据的保留,但除此之外,将它的工作?)

Answer 1:

更新:这个答案是不是.NET 4.5是正确的。 请参见我的博客文章AsyncLocal了解详情。

这里是(在你的问题重复几个点)的情况:

  • LogicalCallContext将与流动async调用; 你可以用它来从设置一些隐式数据和读取它async方法进一步下跌的调用堆栈。
  • 的所有副本LogicalCallContext是浅拷贝,没有任何方式为最终用户代码挂接到深复制样的操作。
  • 当你做“简单的并行”与async ,只有一个副本LogicalCallContext各个之间共享 async方法。

LogicalCallContext 正常工作,如果您的async代码是所有线性:

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").
}

但是,一旦你用我称之为“简单的并行”事情并没有那么好(起始几个async方法,然后使用Task.WaitAll或类似)。 这类似于一个例子我MSDN论坛帖子 (为简单起见,假设一个非平行SynchronizationContext如GUI或ASP.NET):

编辑:代码中的注释是不正确的; 看到这个问题,并回答评论

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").
}

的问题是, LogicalCallContext之间共享 ParentAsyncChildAAsyncChildBAsync ,没有任何方式挂接到或强制深复制操作。 在“线性”示例中,上下文也共享,但只有一个方法是有活性在一个时间。

即使你在存储数据LogicalCallContext是不可变的(在我的整数为例),你仍然必须更新LogicalCallContext为了实现一个NDC值,这意味着该共享不-拷贝的问题是要乱套了起来。

我已经调查对此进行了详细,并得出结论认为,一个解决方案是不可能的。 如果你可以计算一出来,我会很乐意被证明是错误的。 :)

PS斯蒂芬Toub指出,建议使用CallContext仅用于在远程(这没有道理,IIRC给出)不再适用。 我们可以随意使用LogicalCallContext ......如果我们能得到它的工作。 ;)



Answer 2:

斯蒂芬证实,这个工程在.NET 4.5和于Win8 / 2012。 没有测试在其他平台上,和已知的不工作在至少其中的一些。 因此,答案是微软一起得到了他们的游戏,至少在最新版本的.Net和异步编译器固定潜在的问题。

因此,答案是,它的工作,只是没有在旧版本的.Net。 (所以log4net的项目不能使用它提供一个通用的NDC)。



文章来源: Why does LogicalCallContext not work with async?