AsyncLocal是否也可以执行ThreadLocal吗? [英] Does `AsyncLocal` also do the things that `ThreadLocal` does?

查看:89
本文介绍了AsyncLocal是否也可以执行ThreadLocal吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力寻找有关 AsyncLocal< T> 的用途的简单文档.

I'm struggling to find simple docs of what AsyncLocal<T> does.

我写了一些测试,我认为告诉我答案是是",但是如果有人可以确认,那就太好了!(尤其是因为我不知道如何编写对线程和连续上下文具有绝对控制权的测试……因此它们可能只能同时运行!)

I've written some tests which I think tell me that the answer is "yes", but it would great if someone could confirm that! (especially since I don't know how to write tests that would have definitive control of the threads and continuation contexts ... so it's possible that they only work coincidentally!)

  • 据我了解, ThreadLocal 将保证,如果您使用的是其他线程,则将获得对象的其他实例.

  • As I understand it, ThreadLocal will guarantee that if you're on a different thread, then you'll get a different instance of an object.

  • 如果您要创建和结束线程,则可能会稍后再次使用该线程(从而到达已经使用了该线程的" ThreadLocal 对象的线程)一点.)
  • 但是与 await 的交互不太愉快.您继续执行的线程(即使 .ConfigureAwait(true))也不能保证与您启动的线程相同,因此您可能无法从 ThreadLocal .
  • If you're creating and ending threads, then you might end up re-using the thread again later (and thus arriving on a thread where "that thread's" ThreadLocal object has already been used a bit).
  • But the interaction with await is less pleasant. The thread that you continue on (even if .ConfigureAwait(true)) is not guaranteed to be the same thread you started on, thus you may not get the same object back out of ThreadLocal on the otherside.

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

Conversely, AsyncLocal does guarantee that you'll get the same object either side of an await call.

但是我找不到在任何地方说 AsyncLocal 首先将获得特定于初始线程的值!

But I can't find anywhere that actually says that AsyncLocal will get a value that's specific to the initial thread, in the first place!

即:

  • 假设您有一个实例方法( MyAsyncMethod ),该实例方法在其类中引用了来自其类的共享的" AsyncLocal 字段( myAsyncLocal ) await 调用的一侧.
  • 并假设您使用该类的实例,并多次并行调用该方法.*最后,假设每次调用恰好在一个不同的线程上按计划结束.
  • Suppose you have an instance method (MyAsyncMethod) that references a 'shared' AsyncLocal field (myAsyncLocal) from its class, on either side of an await call.
  • And suppose that you take an instance of that class and call that method in parallel a bunch of times. * And suppose finally that each invocation happens to end up scheduled on a distinct thread.

我知道,对于 MyAsyncMethod 的每次单独调用, myAsyncLocal.Value 会在等待之前和之后返回相同的对象(假设什么都没有重新分配)

I know that for each separate invocation of MyAsyncMethod, myAsyncLocal.Value will return the same object before and after the await (assuming that nothing reassigns it)

但是可以确保每个调用都首先查看不同的对象吗?

正如开头提到的,我创建了一个测试来尝试自行确定.以下测试始终通过

As mentioned at the start, I've created a test to try to determine this myself. The following test passes consistently

    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);
        }
    }

推荐答案

但是是否可以确保每个调用都将首先查看不同的对象?

不.从逻辑上将其视为传递给函数的参数(不是 ref out ).调用者将看到对对象的任何更改(例如,设置属性).但是,如果您分配一个新值-呼叫者将不会看到它.

No. Think of it logically like a parameter (not ref or out) you pass to a function. Any changes (e.g. setting properties) to the object will be seen by the caller. But if you assign a new value - it won't be seen by the caller.

所以在您的代码示例中有:

So in your code sample there are:

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 是否是正确的词-但希望您有正确的主意.

I am not sure if context is the right word - but hopefully you get the right idea.

因此,asynclocal将从测试的上下文(就像每个函数的参数一样)流到测试的每个并行foreach调用等的上下文中.这与 ThreadLocal (它不同)不会那样流淌下来.)

So the asynclocal will flow (just like a parameter to a function) from context for the test, down into context for each of the parallel foreach invocations etc etc. This is different to ThreadLocal (it won't flow it down like that).

以您的示例为基础,玩一下:

Building on top of your example, have a play with:

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 >实例).

Notice how backgroundTask is able to see the same async local as the code that invoked it (even though it is from the other thread). It also doesn't impact the calling codes async local string or object - since it re-assigns to them. But the calling code can see its change to Tester (proving that the Task and its calling code share the same Tester instance).

这篇关于AsyncLocal是否也可以执行ThreadLocal吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆