2014-01-24 3 views
3

У меня есть метод, который запускается в основном потоке приложения, но создает новый Task для долговременной работы и await s это результат.Как получить доступ к контексту приложения из потока, который был создан с помощью Task.Factory.StartNew

public async Task<CalculatorOutputModel> CalculateXml(CalculatorInputModel input) 
{ 
    // 1 
    return await Task.Factory.StartNew(
     new Func<CalculatorOutputModel> (() => 
     { 
      // 2 
      using (var ms = Serializer.Serialize(input)) 
      { 
       ms.Position = 0; 
       using (var rawResult = Channel.RawGetFrbXmlOutputs(ms)) // 3 
       { 
        var result = Parser.Parse(rawResult); 
        return result; 
       } 
      } 
     }), 
     CancellationToken.None, 
     TaskCreationOptions.None, 
     TaskScheduler.FromCurrentSynchronizationContext()); 
} 

Моя проблема заключается в том, что в точках контекста приложения (1) и (2) являются "немного" отличается. В пункте 1 есть обычный контекст приложения в свойстве Current, который имеет все необходимые мне данные. В пункте 2, как вы понимаете, есть ссылка null в AppContext.Current, поэтому я не могу получить доступ к каким-либо данным. Проблема с доступом к контексту в точке 2 кажется легкой, просто «поймать» текущий контекст в локальной переменной или передать его в качестве параметра. Проблема для меня сложнее, потому что мне нужно получить доступ к контексту где-то в глубине строки, помеченной как «3».
Сам класс получен из , и место, где мне нужно получить доступ к контексту, является классом, который реализует IClientMessageInspector.

class CalculatorMessageInspector : IClientMessageInspector 
{ 
    public object BeforeSendRequest(ref Message request, IClientChannel channel) 
    { 
     if (AppContext.Current != null) 
     { 
      // never goes here 
     } 
    } 
} 

Просто чтобы прояснить, вот стек вызовов (я не могу передать свой контекст внутри необходимый метод): Call stack

Итак:

  1. Я не могу передать контекст Channel, потому что это не имеет никакого смысла;
  2. Я не могу сохранить требуемые параметры из контекста в Channel, потому что это прокси-класс;
  3. Я не могу сохранить необходимые параметры в CalculatorMessageInspector, потому что он создан в том месте, где текущий контекст уже равен нулю.

Может ли кто-нибудь посоветовать любой метод, как я могу оставаться в том же контексте в другом потоке? Или, по крайней мере, как я могу передать параметр из места, помеченного «2» внутри иерархии методов? Может быть, я смогу использовать SynchronizationContext как-то для его достижения? Большое спасибо за любые предложения.

Update

AppContext.Current считают такой же, как HttpContext.Current != null ? HttpContext.Current.Items[AppContextKey] : null

Update 2 Так что, кажется, что в данном случае нет никакого общего решения сохраняется контекст. Следовательно, единственное применимое решение в этой конкретной ситуации (это довольно специфично) заключается в том, чтобы захватить требуемые параметры с помощью закрытия и сохранить тогда в объекте, который будет доступен в требуемом методе (для меня работали добавление свойств к разработчику IEndpointBehavior, но это решение немного нечетное). Таким образом, наиболее приемлемым решением является выброс асинхронной упаковки по вызову синхронизации, поэтому AppContext никогда не «уходит». Ответ Марка Стивена как раз тогда.

+0

Вы не можете передавать контекст, но можете ли вы изменить подпись метода? Если вы можете, то вы можете передать делегат в BeforeSendRequest(), а затем выполнить некоторый код в этом делетете из блока кода, который имеет доступ к текущему AppContext. –

+2

Что такое 'AppContext'? –

+0

Просто комментарий, вы должны использовать 'Task.Run' с' async-await', как объясняет Стивен здесь: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx –

ответ

2

Чтобы использовать async и await на ASP.NET, вам необходимо настроить .NET 4.5 и отключить режим quirks. Лучший способ сделать это - установить /configuration/system.web/[email protected]="4.5" в свой web.config.

Кроме того, вы не должны использовать Task.Run или Task.Factory.StartNew на ASP.NET.Поэтому ваш код должен выглядеть следующим образом:

public CalculatorOutputModel CalculateXml(CalculatorInputModel input) 
{ 
    using (var ms = Serializer.Serialize(input)) 
    { 
     ms.Position = 0; 
     using (var rawResult = Channel.RawGetFrbXmlOutputs(ms)) 
     { 
      var result = Parser.Parse(rawResult); 
      return result; 
     } 
    } 
} 
+0

Именно так выглядел мой код некоторое время назад. Но в этом случае блок кода в выражении 'using' будет висеть в течение нескольких секунд, потому что вызов этого канала довольно трудоемкий. Это была основная причина, по которой я решил выполнить эти действия в другом потоке. В моем примере ожидается, что основной поток, который вызывает этот метод, продолжает подготовку других запросов и делает их тем временем. Я что-то упускаю? – Eadel

+0

Во-первых, ASP.NET, естественно, многопоточен. Поэтому, если ваш код не делает что-то действительно странное, другие запросы могут обрабатываться другими потоками. Во-вторых, если вы хотите использовать 'async' /' await', то вы хотите вызывать фактический канал асинхронно, а не обертывать синхронный вызов в другом потоке. I.e., если «Channel» является прокси-сервером WCF, вы можете изменить выражение 'using' на' using (var rawResult = await Channel.RawGetFrbXmlOutputsAsync (ms)) '. –

+0

@Eadel прочитал это (http://curah.microsoft.com/44400/async-and-aspnet), чтобы узнать, как работает потоковая обработка в ASP.NET и как он работает с 'async-await'. –

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