Как сохранить контекст потока в модели асинхронного ожидания в C#?

Является ли использование ThreadStatic и установка контекста каждый раз, когда ожидание завершает "опцию"? Есть ли другой способ?

public async void Test()
{
    // This is in Thread 1
    Foo foo = new Foo();
    Context.context = "context1"; // This is ThreadStatic
    string result = await foo.CallAsynx();

    // This is most likely Thread 2
    Context.context = "context1";   // This might be a different thread and so resetting context    
}

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

1 ответ

Решение

ThreadStatic, ThreadLocal<T>, потоковые слоты данных, и CallContext.GetData / CallContext.SetData не работают с async, поскольку они ориентированы на поток.

Лучшие альтернативы:

  1. Передав это в качестве аргумента, как предложил @PauloMorgado. Эквивалентно, вы можете установить его в качестве члена поля объекта (он неявно передается в качестве аргумента через this); или вы можете сделать так, чтобы ваши лямбда-выражения захватывали переменную (внизу компилятор неявно передает ее в качестве аргумента через this).
  2. использование HttpContext.Items (если вы используете ASP.NET 4.5).
  3. использование CallContext.LogicalGetData / CallContext.LogicalSetData как предложено @Noseratio. Вы можете хранить только неизменные данные в контексте логического потока; и он работает только на.NET 4.5 и доступен не на всех платформах (например, Win8).
  4. Заставить всех async продолжения обратно в тот же поток, установив для этого потока "основной цикл", такой как AsyncContext из моей библиотеки AsyncEx.

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

Появилась новая функция под названием

AsyncLocal<T>

https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1?view=netcore-3.1

Это работает с "async/await", а также с:

  • Task.Run (...)
  • Dispatcher.BeginInvoke (...)
  • новый поток (...).Start()

Я просто протестировал эти три с помощью следующего кода:

    private void StartTests() {
        Thread.Sleep(1000);
        Task.Run(() => DoWork1());
        Task.Run(() => DoWork2());
    }

    private void DoWork1() {
        ThreadContext.Context.Value = "Work 1";
        Thread.Sleep(5);
        Task.Run(() => PrintContext("1"));
        Thread.Sleep(10);
        Dispatcher.BeginInvoke(new Action(() => PrintContext("1")));
        Thread.Sleep(15);
        var t = new Thread(() => PrintContextT("1"));
        t.Start();
    }

    private void DoWork2() {
        ThreadContext.Context.Value = "Work 2";
        Task.Run(() => PrintContext("2"));
        Thread.Sleep(10);
        Dispatcher.BeginInvoke(new Action(() => PrintContext("2")));
        Thread.Sleep(10);
        var t = new Thread(() => PrintContextT("2"));
        t.Start();
    }

    private void PrintContext(string c) {
        var context = ThreadContext.Context.Value;
        Console.WriteLine("P: " + context + "-" + c);

        Task.Run(() => PrintContext2(c));
    }

    private void PrintContext2(string c) {
        Thread.Sleep(7);
        var context = ThreadContext.Context.Value;
        Console.WriteLine("P2: " + context + "-" + c);
    }

    private void PrintContextT(string c) {
        var context = ThreadContext.Context.Value;
        Console.WriteLine("T: " + context + "-" + c);
    }

    public class ThreadContext {
        public static AsyncLocal<object> Context = new AsyncLocal<object>();
    }

Выход:

П: Работа 2-2

П: Работа 1-1

P2: Работа 2-2

П: Работа 2-2

P2: Работа 1-1

П: Работа 1-1

P2: Работа 2-2

Т: Работа 2-2

P2: Работа 1-1

Т: Работа 1-1

Другие вопросы по тегам