gpt4 book ai didi

c# - `AsyncLocal` 是否也做 `ThreadLocal` 做的事情?

转载 作者:行者123 更新时间:2023-12-03 12:53:53 25 4
gpt4 key购买 nike

我正在努力寻找 AsyncLocal<T> 功能的简单文档。
我写了一些我认为告诉我答案是"is"的测试,但如果有人能证实这一点就太好了! (特别是因为我不知道如何编写可以明确控制线程和延续上下文的测试......所以它们可能只是巧合地工作!)

  • 据我所知,ThreadLocal 将保证如果您在不同的线程上,那么您将获得对象的不同实例。
  • 如果您正在创建和结束线程,那么您可能会在稍后再次重新使用该线程(从而到达“该线程的” ThreadLocal 对象已经使用了一点的线程)。
  • 但与 await 的交互不太愉快。您继续使用的线程(即使 .ConfigureAwait(true) )不能保证与您开始使用的线程相同,因此您可能无法从另一端的 ThreadLocal 中取回相同的对象。

  • 相反,AsyncLocal 确实保证您将在 await 调用的任一侧获得相同的对象。

  • 但是我找不到任何地方说 AsyncLocal 首先会获得特定于初始线程的值!
    IE。:
  • 假设您有一个实例方法 (MyAsyncMethod),它在 AsyncLocal 调用的任一侧引用其类中的“共享”myAsyncLocal 字段 (await)。
  • 并且假设您获取该类的一个实例并并行调用该方法很多次。 * 最后假设每次调用碰巧最终被安排在一个不同的线程上。

  • 我知道对于 MyAsyncMethod 的每次单独调用, myAsyncLocal.Value 将在 await 之前和之后返回相同的对象(假设没有重新分配它)
    但是它是否保证每个调用首先会查看不同的对象?

    正如开头提到的,我创建了一个测试来尝试自己确定这一点。以下测试一致通过
        public class AssessBehaviourOfAsyncLocal
    {
    private class StringHolder
    {
    public string HeldString { get; set; }
    }

    [Test, Repeat(10)]
    public void RunInParallel()
    {
    var reps = Enumerable.Range(1, 100).ToArray();
    Parallel.ForEach(reps, index =>
    {
    var val = "Value " + index;
    Assert.AreNotEqual(val, asyncLocalString.Value?.HeldString);
    if (asyncLocalString.Value == null)
    {
    asyncLocalString.Value = new StringHolder();
    }
    asyncLocalString.Value.HeldString = val;
    ExamineValuesOfLocalObjectsEitherSideOfAwait(val).Wait();
    });
    }

    static readonly AsyncLocal<StringHolder> asyncLocalString = new AsyncLocal<StringHolder>();

    static async Task ExamineValuesOfLocalObjectsEitherSideOfAwait(string expectedValue)
    {
    Assert.AreEqual(expectedValue, asyncLocalString.Value.HeldString);
    await Task.Delay(100);
    Assert.AreEqual(expectedValue, asyncLocalString.Value.HeldString);
    }
    }

    最佳答案

    But is it guaranteed that each of the invocations will be looking at different objects in the first place?不。在逻辑上将其视为传递给函数的参数(不是 refout )。调用者将看到对象的任何更改(例如设置属性)。但是,如果您分配一个新值 - 调用者将看不到它。
    所以在你的代码示例中有:

    Context for the test
    -> Context for each of the parallel foreach invocations (some may be "shared" between invocations since parallel will likely reuse threads)
    -> Context for the ExamineValuesOfLocalObjectsEitherSideOfAwait invocation
    我不确定 context 是否是正确的词 - 但希望你能得到正确的想法。
    因此 asynclocal 将从测试的上下文(就像函数的参数一样)流入每个并行 foreach 调用的上下文等等。这与 ThreadLocal 不同(它不会像那样向下流动)。
    以您的示例为基础,玩一下:
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using NUnit.Framework;

    namespace NUnitTestProject1
    {
    public class AssessBehaviourOfAsyncLocal
    {
    public class Tester
    {
    public int Value { get; set; }
    }

    [Test, Repeat(50)]
    public void RunInParallel()
    {
    var newObject = new object();
    var reps = Enumerable.Range(1, 5);
    Parallel.ForEach(reps, index =>
    {
    //Thread.Sleep(index * 50); (with or without this line,
    Assert.AreEqual(null, asyncLocalString.Value);
    asyncLocalObject.Value = newObject;
    asyncLocalTester.Value = new Tester() { Value = 1 };

    var backgroundTask = new Task(() => {
    Assert.AreEqual(null, asyncLocalString.Value);
    Assert.AreEqual(newObject, asyncLocalObject.Value);
    asyncLocalString.Value = "Bobby";
    asyncLocalObject.Value = "Hello";
    asyncLocalTester.Value.Value = 4;

    Assert.AreEqual("Bobby", asyncLocalString.Value);
    Assert.AreNotEqual(newObject, asyncLocalObject.Value);
    });

    var val = "Value " + index;
    asyncLocalString.Value = val;
    Assert.AreEqual(newObject, asyncLocalObject.Value);
    Assert.AreEqual(1, asyncLocalTester.Value.Value);

    backgroundTask.Start();
    backgroundTask.Wait();
    // Note that the Bobby is not visible here
    Assert.AreEqual(val, asyncLocalString.Value);
    Assert.AreEqual(newObject, asyncLocalObject.Value);
    Assert.AreEqual(4, asyncLocalTester.Value.Value);

    ExamineValuesOfLocalObjectsEitherSideOfAwait(val).Wait();
    });
    }

    static readonly AsyncLocal<string> asyncLocalString = new AsyncLocal<string>();
    static readonly AsyncLocal<object> asyncLocalObject = new AsyncLocal<object>();
    static readonly AsyncLocal<Tester> asyncLocalTester = new AsyncLocal<Tester>();

    static async Task ExamineValuesOfLocalObjectsEitherSideOfAwait(string expectedValue)
    {
    Assert.AreEqual(expectedValue, asyncLocalString.Value);
    await Task.Delay(100);
    Assert.AreEqual(expectedValue, asyncLocalString.Value);
    }
    }
    }
    请注意 backgroundTask 如何能够看到与调用它的代码相同的异步本地(即使它来自另一个线程)。它也不会影响调用代码异步本地字符串或对象 - 因为它重新分配给它们。但是调用代码可以看到它对 Tester 的改变(证明 Task 和它的调用代码共享同一个 Tester 实例)。

    关于c# - `AsyncLocal` 是否也做 `ThreadLocal` 做的事情?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63073439/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com