Утилизация потока статической переменной - PullRequest
1 голос
/ 04 июня 2019

У меня есть ThreadStatic член в статическом классе.Статический класс используется в многопоточной среде.Я хочу убедиться, что когда поток возвращается в пул потоков (или используется повторно), член удаляется (или повторно инициализируется), поэтому любое последующее использование конкретного потока получает свежую копию переменной.Участник должен оставаться статичным, поэтому член экземпляра на самом деле не поможет.

Я пытался использовать ThreadLocal, AsyncLocal и CallContext, но ничего из этого не помогло.(CallContext в основном для проверки концепции, это стандартное приложение .net, поэтому callcontext не будет работать в любом случае).

Это просто пример кода, который я написал, чтобы воссоздать мою проблему с ThreadStatic, ThreadLocal, AsyncLocal и CallContext для тестирования.


    class Program
    {
        static void Main(string[] args)
        {
            var act = new List<Action<int>>()
            {
                v=> ThreadClass.Write(v),
                v=> ThreadClass.Write(v),
            };

            Parallel.ForEach(act, new ParallelOptions { MaxDegreeOfParallelism = 1 }, (val, _, index) => val((int)index));

            Console.WriteLine($"Main: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadClass.ThreadStatic} ThreadLocal = {ThreadClass.ThreadLocal.Value} AsyncLocal = {ThreadClass.AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");

            Console.ReadKey();
        }
    }

    public static class ThreadClass
    {
        static object _lock = new object();

        [ThreadStatic]
        public static string ThreadStatic;

        public static ThreadLocal<string> ThreadLocal = new ThreadLocal<string>(() => "default");


        public static readonly AsyncLocal<string> AsyncLocal = new AsyncLocal<string>();

        public static string CallContextData
        {
            get => CallContext.LogicalGetData("value") as string;
            set => CallContext.LogicalSetData("value", value);
        }

        static ThreadClass()
        {
            AsyncLocal.Value = "default";
        }


        public static void Write(int id)
        {
            lock (_lock)
            {
                Console.WriteLine($"{id} Init: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadStatic} ThreadLocal = {ThreadLocal.Value} AsyncLocal = {AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");

                ThreadStatic = $"Static({id})";
                ThreadLocal.Value = $"Local({id})";
                AsyncLocal.Value = $"Async({id})";
                CallContextData = $"Call({id})";

                Console.WriteLine($"{id} Chng: ThreadId: {Thread.CurrentThread.ManagedThreadId} ThreadStatic = {ThreadStatic} ThreadLocal = {ThreadLocal.Value} AsyncLocal = {AsyncLocal.Value} CallContext: {ThreadClass.CallContextData}");
            }

        }
    }

Приведенный выше код выполняется в одном потоке, поэтому этот поток можно использовать повторно.

0 Init: ThreadId: 1 ThreadStatic =  ThreadLocal = default AsyncLocal = default CallContext:
0 Chng: ThreadId: 1 ThreadStatic = Static(0) ThreadLocal = Local(0) AsyncLocal = Async(0) CallContext: Call(0)
--------------------
1 Init: ThreadId: 1 ThreadStatic = Static(0) ThreadLocal = Local(0) AsyncLocal = Async(0) CallContext: Call(0)
1 Chng: ThreadId: 1 ThreadStatic = Static(1) ThreadLocal = Local(1) AsyncLocal = Async(1) CallContext: Call(1)
--------------------
Main: ThreadId: 1 ThreadStatic = Static(1) ThreadLocal = Local(1) AsyncLocal =  CallContext:

Однако, как видно из выходных данных, когда выполняется второй вызови поток 1 используется повторно, он все еще имеет значения, установленные потоком 0.

Есть ли способ вернуть ThreadStatic значение переменной в значение по умолчанию или ноль, когда поток используется повторно?

1 Ответ

1 голос
/ 04 июня 2019

TL; DR

Если вы не хотите, чтобы переменная повторно использовалась несколькими потоками в многопоточном приложении, нет причин делать ее статичной.

Если мы не хотимпеременная, повторно используемая тем же потоком, сомнительно, почему мы намеренно использовали [ThreadStatic], поскольку это то, что она позволяет нам делать.


Я сосредоточен на аспекте ThreadStatic этого, посколькукажется, что он находится в центре внимания вопроса.

, поэтому любое последующее использование определенного потока получает новую копию переменной.

Использование потока don 'Не требуется собственная копия переменной. Методы , использующие переменную , могут нуждаться или не нуждаться в собственной копии переменной.Это звучит как потрясающая вещь, но поток сам по себе не нуждается в копии какой-либо переменной.Это может делать вещи, не связанные с этим статическим классом и этой переменной.

Когда мы используем переменную, мы заботимся, является ли она "свежей копией".То есть, когда мы вызываем метод, который использует переменную.

Если, когда мы используем статическую переменную (которая объявлена ​​вне метода), мы хотим убедиться, что она была создана заново, прежде чем мы используемэто и распоряжаться, когда мы закончили с этим, тогда мы можем сделать это в рамках метода, который его использует.Мы можем создать его, утилизировать или даже установить на null, если захотим.Однако, когда мы делаем это, становится очевидным, что это обычно исключает необходимость объявления переменной вне метода, который ее использует.

Если мы делаем это:

public static class HasDisposableThreadStaticThing
{
    [ThreadStatic]
    public static DisposableThing Foo;

    public static void UseDisposableThing()
    {
        try
        {
            using (Foo = new DisposableThing())
            {
                Foo.DoSomething();
            }
        }
        finally
        {
            Foo = null;
        }
    }
}

Мы достигли цели.

Можно ли каким-либо образом сбросить переменную ThreadStatic к значению по умолчанию или к нулю при повторном использовании потока?

Готово.Каждый раз, когда один и тот же поток входит в метод («поток используется повторно»), он становится нулевым.

Но если это то, что мы хотим, то почему бы просто не сделать это?

public static class HasDisposableThreadStaticThing
{
    public static void UseDisposableThing()
    {
        using (var foo = new DisposableThing())
        {
            foo.DoSomething();
        }
    }
}

Результат точно такой же.Каждый поток начинается с нового экземпляра DisposableThing, потому что когда он выполняет метод, он объявляет переменную и создает новый экземпляр.Вместо установки null ссылка выходит из области видимости.

Единственное различие между ними состоит в том, что в первом примере DisposableThing открыто выставляется вне класса.Это означает, что другие потоки могут использовать его вместо объявления своей собственной переменной, что странно.Поскольку им также необходимо убедиться, что он создан, прежде чем его использовать, почему бы им просто не создать свой собственный экземпляр, как во втором примере?

Самый простой и нормальный способ обеспечить инициализацию переменнойи каждый раз, когда это необходимо в статическом методе, он объявляет эту переменную локально в статическом методе и создает новый экземпляр.Затем независимо от того, сколько потоков вызывает его одновременно, каждый из них будет использовать отдельный экземпляр.

...