我在.NET 4.5中使用async / await模式在WCF中实现一些服务方法。 示例服务:
合同:
[ServiceContract(Namespace = "http://async.test/")]
public interface IAsyncTest
{
Task DoSomethingAsync();
}
实现:
MyAsyncService : IAsyncTest
{
public async Task DoSomethingAsync()
{
var context = OperationContext.Current; // context is present
await Task.Delay(10);
context = OperationContext.Current; // context is null
}
}
我遇到的问题是,首先await
OperationContext.Current
返回null
后我无法访问OperationContext.Current.IncomingMessageHeaders
。
在这个简单的例子中,这不是问题,因为我可以在await
之前捕获上下文。但是在现实世界中,OperationContext.Current
是从调用堆栈深处访问的,我真的不想更改大量代码只是为了进一步传递上下文。
有没有办法在await
点之后获取操作上下文而不是手动将其传递给堆栈?
答案 0 :(得分:28)
不幸的是,这不起作用,我们将在未来的版本中看到修复。
同时,有一种方法可以将上下文重新应用于当前线程,这样您就不必传递对象:
public async Task<double> Add(double n1, double n2)
{
OperationContext ctx = OperationContext.Current;
await Task.Delay(100);
using (new OperationContextScope(ctx))
{
DoSomethingElse();
}
return n1 + n2;
}
在上面的示例中, DoSomethingElse()方法可以按预期访问OperationContext.Current。
答案 1 :(得分:21)
我认为你最好的选择是实际捕获它并手动传递它。您可能会发现这会提高代码的可测试性。
尽管如此,还有其他几种选择:
LogicalCallContext
。SynchronizationContext
,OperationContext.Current
时会设置Post
;这就是ASP.NET保留其HttpContext.Current
。TaskScheduler
的{{1}}。您可能还想在Microsoft Connect上引发此问题。
答案 2 :(得分:7)
似乎是在.Net 4.6.2中修复的。见the announcement
答案 3 :(得分:3)
以下是SynchronizationContext
实施示例:
public class OperationContextSynchronizationContext : SynchronizationContext
{
private readonly OperationContext context;
public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { }
public OperationContextSynchronizationContext(OperationContext context)
{
OperationContext.Current = context;
this.context = context;
}
public override void Post(SendOrPostCallback d, object state)
{
OperationContext.Current = context;
d(state);
}
}
用法:
var currentSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel));
var response = await client.RequestAsync();
// safe to use OperationContext.Current here
}
finally
{
SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext);
}
答案 4 :(得分:3)
扩展Cleary先生的#1选项,可以将以下代码放在WCF服务的构造函数中,以在逻辑调用上下文中存储和检索OperationContext
:
if (CallContext.LogicalGetData("WcfOperationContext") == null)
{
CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current);
}
else if (OperationContext.Current == null)
{
OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext");
}
有了这个,无论你遇到空上下文的问题,你都可以写下如下内容:
var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext;
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available";
免责声明:这是一年之久的代码,我不记得我在构造函数中需要else if
的原因,但它与异步有关,我知道在我的情况下需要它
答案 5 :(得分:2)
幸运的是,我们的实际服务实现通过Unity
IoC容器进行实例化。这使我们能够创建IWcfOperationContext
,其中PerResolveLifetimeManager
配置为WcfOperationContext
,这意味着我们RealService
的每个实例只会有WcfOperationContext
个实例。<登记/>
在OperationContext.Current
的构造函数中,我们捕获IWcfOperationContext
,然后所有需要它的地方从{{1}}获取它。这实际上是Stephen Cleary在答案中提出的建议。
答案 6 :(得分:-2)
更新:正如下面的评论所指出的,这个解决方案不是线程安全的,所以我猜上面讨论的解决方案仍然是最好的方法。
我通过将HttpContext注册到我的DI容器(Application_BeginRequest)并在需要时解决它来解决问题。
寄存器:
this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current));
消退:
var context = Dependencies.ResolveInstance<HttpContextBase>();