Как сохранить контекст потока в модели асинхронного ожидания в 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
, поскольку они ориентированы на поток.
Лучшие альтернативы:
- Передав это в качестве аргумента, как предложил @PauloMorgado. Эквивалентно, вы можете установить его в качестве члена поля объекта (он неявно передается в качестве аргумента через
this
); или вы можете сделать так, чтобы ваши лямбда-выражения захватывали переменную (внизу компилятор неявно передает ее в качестве аргумента черезthis
). - использование
HttpContext.Items
(если вы используете ASP.NET 4.5). - использование
CallContext.LogicalGetData
/CallContext.LogicalSetData
как предложено @Noseratio. Вы можете хранить только неизменные данные в контексте логического потока; и он работает только на.NET 4.5 и доступен не на всех платформах (например, Win8). - Заставить всех
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