gab*_*ldi 59 .net c# asp.net wcf async-await
我使用基于任务的操作生成了代理.
如何使用async/await 正确调用此服务(处理ServiceClient和OperationContext之后)?
我的第一次尝试是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
Run Code Online (Sandbox Code Playgroud)
作为ServiceHelper一个创造ServiceClient和OperationContextScope后来处理它们的类:
try
{
if (_operationContextScope != null)
{
_operationContextScope.Dispose();
}
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_operationContextScope = null;
_serviceClient = null;
}
Run Code Online (Sandbox Code Playgroud)
但是,当同时调用两个服务时出现以下错误,这种情况很糟糕:"此OperationContextScope正在处理与创建时不同的线程."
MSDN说:
不要在OperationContextScope块中使用异步"await"模式.当继续发生时,它可以在不同的线程上运行,而OperationContextScope是特定于线程的.如果需要为异步调用调用"await",请在OperationContextScope块之外使用它.
这就是问题所在!但是,我们如何正确地修复它?
private async void DoStuffWithDoc(string docId)
{
var doc = await GetDocumentAsync(docId);
if (doc.YadaYada)
{
// more code here
}
}
public Task<Document> GetDocumentAsync(string docId)
{
var docClient = CreateDocumentServiceClient();
using (new OperationContextScope(docClient.InnerChannel))
{
return docClient.GetDocumentAsync(docId);
}
}
Run Code Online (Sandbox Code Playgroud)
我的代码问题是他从不在ServiceClient上调用Close(或Abort).
我还找到了一种OperationContextScope使用自定义传播的方法SynchronizationContext.但是,除了它是很多"有风险"的代码之外,他还指出:
值得注意的是,它确实有一些关于操作上下文范围处理的小问题(因为它们只允许你将它们放在调用线程上),但这似乎不是一个问题,因为(至少根据反汇编),它们实现Dispose()但不实现Finalize().
那么,我们这里运气不好吗?是否存在使用async/await调用WCF服务的已证明模式ServiceClient并且将两者一起处理OperationContextScope?也许某人形成微软(也许是大师Stephen Toub :))可以提供帮助.
谢谢!
[UPDATE]
在Noseratio用户的帮助下,我想出了一些有用的东西:不要使用OperationContextScope.如果你正在使用它任何的这些理由,试图找到适合您的方案一种解决方法.否则,如果你真的,真的,需要OperationContextScope,你将不得不想出一个SynchronizationContext捕获它的实现,这似乎非常困难(如果可能的话 - 必须有一个原因,为什么这不是默认行为).
所以,完整的工作代码是:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
Run Code Online (Sandbox Code Playgroud)
随着ServiceHelper:
public class ServiceHelper<TServiceClient, TService> : IDisposable
where TServiceClient : ClientBase<TService>, new()
where TService : class
{
protected bool _isInitialized;
protected TServiceClient _serviceClient;
public TServiceClient Proxy
{
get
{
if (!_isInitialized)
{
Initialize();
_isInitialized = true;
}
else if (_serviceClient == null)
{
throw new ObjectDisposedException("ServiceHelper");
}
return _serviceClient;
}
}
protected virtual void Initialize()
{
_serviceClient = new TServiceClient();
}
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void Dispose()
{
Dispose(true);
// Take yourself off the Finalization queue
// to prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
// If disposing equals true, dispose all managed
// and unmanaged resources.
if (disposing)
{
try
{
if (_serviceClient != null)
{
if (_serviceClient.State != CommunicationState.Faulted)
{
_serviceClient.Close();
}
else
{
_serviceClient.Abort();
}
}
}
catch (CommunicationException)
{
_serviceClient.Abort();
}
catch (TimeoutException)
{
_serviceClient.Abort();
}
catch (Exception)
{
_serviceClient.Abort();
throw;
}
finally
{
_serviceClient = null;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,该类支持扩展; 也许你需要继承并提供证书.
唯一可能的"问题"在于GetHomeInfoAsync,你不能只从Task你的代理中返回你得到的东西(这看起来应该是自然的,为什么Task你已经拥有它时创建一个新东西).那么,在这种情况下,你需要await代理Task,并随后关闭(或终止)的ServiceClient,否则你会被关闭它调用服务(而字节正在通过网络发送)后,马上!
好吧,我们有办法让它发挥作用,但是从权威来源得到答案会很好,正如Noseratio所说.
nos*_*tio 31
我认为一个可行的解决方案可能是使用自定义awaiter来通过新的操作上下文OperationContext.Current.它本身的实现OperationContext似乎不需要线程亲和力.这是模式:
async Task TestAsync()
{
using(var client = new WcfAPM.ServiceClient())
using (var scope = new FlowingOperationContextScope(client.InnerChannel))
{
await client.SomeMethodAsync(1).ContinueOnScope(scope);
await client.AnotherMethodAsync(2).ContinueOnScope(scope);
}
}
Run Code Online (Sandbox Code Playgroud)
这是FlowingOperationContextScope和ContinueOnScope(仅经过轻微测试)的实现:
public sealed class FlowingOperationContextScope : IDisposable
{
bool _inflight = false;
bool _disposed;
OperationContext _thisContext = null;
OperationContext _originalContext = null;
public FlowingOperationContextScope(IContextChannel channel):
this(new OperationContext(channel))
{
}
public FlowingOperationContextScope(OperationContext context)
{
_originalContext = OperationContext.Current;
OperationContext.Current = _thisContext = context;
}
public void Dispose()
{
if (!_disposed)
{
if (_inflight || OperationContext.Current != _thisContext)
throw new InvalidOperationException();
_disposed = true;
OperationContext.Current = _originalContext;
_thisContext = null;
_originalContext = null;
}
}
internal void BeforeAwait()
{
if (_inflight)
return;
_inflight = true;
// leave _thisContext as the current context
}
internal void AfterAwait()
{
if (!_inflight)
throw new InvalidOperationException();
_inflight = false;
// ignore the current context, restore _thisContext
OperationContext.Current = _thisContext;
}
}
// ContinueOnScope extension
public static class TaskExt
{
public static SimpleAwaiter<TResult> ContinueOnScope<TResult>(this Task<TResult> @this, FlowingOperationContextScope scope)
{
return new SimpleAwaiter<TResult>(@this, scope.BeforeAwait, scope.AfterAwait);
}
// awaiter
public class SimpleAwaiter<TResult> :
System.Runtime.CompilerServices.INotifyCompletion
{
readonly Task<TResult> _task;
readonly Action _beforeAwait;
readonly Action _afterAwait;
public SimpleAwaiter(Task<TResult> task, Action beforeAwait, Action afterAwait)
{
_task = task;
_beforeAwait = beforeAwait;
_afterAwait = afterAwait;
}
public SimpleAwaiter<TResult> GetAwaiter()
{
return this;
}
public bool IsCompleted
{
get
{
// don't do anything if the task completed synchronously
// (we're on the same thread)
if (_task.IsCompleted)
return true;
_beforeAwait();
return false;
}
}
public TResult GetResult()
{
return _task.Result;
}
// INotifyCompletion
public void OnCompleted(Action continuation)
{
_task.ContinueWith(task =>
{
_afterAwait();
continuation();
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
SynchronizationContext.Current != null ?
TaskScheduler.FromCurrentSynchronizationContext() :
TaskScheduler.Current);
}
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
简单的方法是将等待移动到using块之外
public Task<Document> GetDocumentAsync(string docId)
{
var docClient = CreateDocumentServiceClient();
using (new OperationContextScope(docClient.InnerChannel))
{
var task = docClient.GetDocumentAsync(docId);
}
return await task;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
52706 次 |
| 最近记录: |