2012-10-22 2 views
25

С новыми ключевыми словами async/wait в C# теперь есть влияние на способ (и когда) вы используете данные ThreadStatic, потому что делегат callback выполняется в другом потоке на один async началась операция. Например, следующий простой консоли приложение:с использованием переменных ThreadStatic с async/await

[ThreadStatic] 
private static string Secret; 

static void Main(string[] args) 
{ 
    Start().Wait(); 
    Console.ReadKey(); 
} 

private static async Task Start() 
{ 
    Secret = "moo moo"; 
    Console.WriteLine("Started on thread [{0}]", Thread.CurrentThread.ManagedThreadId); 
    Console.WriteLine("Secret is [{0}]", Secret); 

    await Sleepy(); 

    Console.WriteLine("Finished on thread [{0}]", Thread.CurrentThread.ManagedThreadId); 
    Console.WriteLine("Secret is [{0}]", Secret); 
} 

private static async Task Sleepy() 
{ 
    Console.WriteLine("Was on thread [{0}]", Thread.CurrentThread.ManagedThreadId); 
    await Task.Delay(1000); 
    Console.WriteLine("Now on thread [{0}]", Thread.CurrentThread.ManagedThreadId); 
} 

будет выводить что-то вдоль линии:

Started on thread [9] 
Secret is [moo moo] 
Was on thread [9] 
Now on thread [11] 
Finished on thread [11] 
Secret is [] 

Я также экспериментировали с использованием CallContext.SetData и CallContext.GetData и получил такое же поведение.

После прочтения некоторые связанные вопросы и темы:

, кажется, что рамки, как ASP.Net явно переносит HttpContext через потоки, но не CallContext, так что, возможно, то же самое происходит здесь с использованием async и await ключевые слова?

С использованием ключевых слов async/wait, что лучше всего хранить данные, связанные с конкретным потоком выполнения, который может быть (автоматически!) Восстановлен в потоке обратного вызова?

Спасибо,

ответ

9

В принципе, я хотел бы подчеркнуть: не делайте этого. [ThreadStatic] никогда не будет хорошо играть с кодом, который перескакивает между потоками.

Но вам не обязательно. Task уже несет государство - на самом деле, он может сделать это 2 способами:

  • есть явное состояние объекта, который может вместить все, что нужно
  • лямбды/Анон-методы могут образовывать закрытия над состоянием

Кроме того, компилятор делает все, что вам не нужно здесь в любом случае:

private static async Task Start() 
{ 
    string secret = "moo moo"; 
    Console.WriteLine("Started on thread [{0}]", 
     Thread.CurrentThread.ManagedThreadId); 
    Console.WriteLine("Secret is [{0}]", secret); 

    await Sleepy(); 

    Console.WriteLine("Finished on thread [{0}]", 
     Thread.CurrentThread.ManagedThreadId); 
    Console.WriteLine("Secret is [{0}]", secret); 
} 

нет статического состояния; нет проблем с потоками или несколькими задачами. Это только работает. Обратите внимание, что secret здесь не просто «локальный»; компилятор работал несколько вуду, как и с итераторными блоками и захваченными переменными. Проверяя отражатель, я получаю:

[CompilerGenerated] 
private struct <Start>d__0 : IAsyncStateMachine 
{ 
    // ... lots more here not shown 
    public string <secret>5__1; 
} 
+0

Что в случае с WCF? должен ли я просто использовать «OperationContext» вместо этого, при условии, что он будет перенесен на новый поток? – theburningmonk

+0

@ theburningmonk, если вы имеете в виду * экземпляр *, тогда это должно работать. Но я сомневаюсь, что статический 'OperationContext.Current' будет работать правильно. Итак, 'var ctx = OperationContext.Current;' вверху (в исходном потоке), а затем ссылаться только на 'ctx', а не на' OperationContext.Current' –

+0

, поэтому вы говорите, что если вы не захватили текущий 'OperationContext 'в закрытии, как и раньше' await', вы не получите тот же экземпляр 'OperationContext' после' await'? – theburningmonk

7

Получение продолжения задачи для выполнения в одном потоке требует поставщика синхронизации. Это дорогое слово, простая диагностика - это посмотреть на значение System.Threading.SynchronizationContext.Current в отладчике.

Это значение будет null в режиме консоли приложение. Существует не провайдер, который может сделать код запуска в определенном потоке в приложении режима консоли. Только Winforms или WPF-приложение или ASP.NET приложение будет иметь провайдера. И только по их основной теме.

Основная тема этих приложений делает что-то особенное, у них есть диспетчерский контур (например, контур сообщения или насос сообщений). Что реализует общее решение для producer-consumer problem. Это тот цикл диспетчера, который позволяет передавать потоку некоторую работу для выполнения. Такая небольшая работа будет продолжением задачи после выражения ожидания. И этот бит будет работать на поток диспетчера.

WindowsFormsSynchronizationContext - поставщик синхронизации для приложения Winforms. Он использует Control.Begin/Invoke() для отправки запроса. Для WPF это класс DispatcherSynchronizationContext, он использует Dispatcher.Begin/Invoke() для отправки запроса. Для ASP.NET это класс AspNetSynchronizationContext, он использует невидимую внутреннюю сантехнику. Они создают экземпляр своих соответствующих поставщиков при их инициализации и назначают его SynchronizationContext.Current

Нет такого провайдера для приложения в консольном режиме. Прежде всего потому, что основной поток полностью непригоден, он не использует цикл диспетчера. Вы бы создали свой собственный, а затем создали свой собственный производный класс SynchronizationContext. Трудно сделать, вы не можете сделать вызов, например Console.ReadLine(), поскольку это полностью замораживает основной поток при вызове Windows. Приложение с консольным режимом перестает быть консольным приложением, оно начнет напоминать приложение Winforms.

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

+0

есть эквивалентный поставщик синхронизации в WCF? – theburningmonk

+0

№. –

20

Вы мог использовать CallContext.LogicalSetData и CallContext.LogicalGetData, но я рекомендую вам не потому, что они не поддерживают какие-либо «клонирование» при использовании простого параллелизма (Task.WhenAny/Task.WhenAll).

Я открыл UserVoice request для более полного async-совместимый «контекст», более подробно объясненный в an MSDN forum post. Кажется невозможным построить один самостоятельно. У Джона Скита есть good blog entry по этому вопросу.

Итак, я рекомендую использовать аргумент, лямбда-закрытие или члены локального экземпляра (this), как описано в Marc.

И да, OperationContext.Current is не сохранено через await s.

Обновление:. NET 4.5 поддерживает Logical[Get|Set]Data в async. Подробности on my blog.

0

Посмотрите на эту thread

на полях, отмеченных ThreadStaticAttribute, будет инициализация происходит только один раз. В вашем коде, когда будет создан новый поток с ID 11, будет создано новое секретное поле, но оно будет пустым/нулевым, при возврате в задачу «Начать» задача завершится в потоке 11 (как показывает ваша распечатка), и поэтому строка пусто.

Вы можете решить вашу проблему, сохранив секрет в локальном поле внутри «Пуск» непосредственно перед вызовом «Сонный», а затем верните секретный текст из локального поля после возвращения из «Сонного». Вы также можете сделать это в Sleepy как раз перед тем, как позвонить «Ожидание Task.Delay (1000)»; что фактически вызывает переключатель потока.

Смежные вопросы