使用async / await调用WCF服务的模式

我使用基于任务的操作生成代理。

应该如何正确调用这个服务(之后处理ServiceClientOperationContext )使用async / await?

我的第一个尝试是:

 public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } } 

作为ServiceHelper创buildServiceClientOperationContextScope的类,然后处理它们:

 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; } 

但是,在调用两个服务的同时出现以下错误时失败了:“此OperationContextScope被放置在与创build不同的线程上。

MSDN说:

不要在OperationContextScope块中使用asynchronous的“await”模式。 当继续发生时,它可能运行在不同的线程上,OperationContextScope是线程特定的。 如果您需要调用“await”进行asynchronous调用,请在OperationContextScope块之外使用它。

所以这就是问题所在! 但是,我们如何正确解决?

这个人正是MSDN所说的 :

 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); } } 

我的代码问题是,他从来没有在ServiceClient上调用Close(或Abort)。

我还find了一种使用自定义SynchronizationContext来传播OperationContextScope方法。 但是,除了有很多“风险”的代码外,他还说:

值得注意的是,它在操作上下文范围的处置方面确实有一些小问题(因为它们只允许你把它们放在调用线程上),但这似乎不是一个问题,因为(至less根据他们实现了Dispose()而不是Finalize()。

那么,我们在这里运气不好吗? 有没有一种经过validation的模式调用WCF服务使用asynchronous/等待和处置的ServiceClientOperationContextScope ? 也许有人形成微软(也许大师斯蒂芬TOUB :)可以帮助。

谢谢!

[UPDATE]

在用户Noseratio的帮助下,我想出了一些可行的方法:不要使用OperationContextScope 。 如果您因为这些原因而使用了该function,请尝试find适合您的scheme的解决方法。 否则,如果你确实需要OperationContextScope ,你将不得不拿出一个SynchronizationContext的实现来捕获它,而这似乎很难 (如果可能的话 – 必须有一个原因,为什么这不是默认行为)。

所以,完整的工作代码是:

 public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return await helper.Proxy.GetHomeInfoAsync(timestamp); } } 

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; } } } } 

请注意,该类支持扩展; 也许你需要inheritance并提供凭据。

唯一可能的问题是,在GetHomeInfoAsync ,你不能只返回你从代理中得到的Task (这应该看起来很自然,为什么创build一个新的Task当你已经有一个)。 那么,在这种情况下,您需要await代理Task然后closures(或中止) ServiceClient ,否则您将在调用服务后立即closures它(而字节通过电线发送)!

好的,我们有办法让它工作,但是如果你能从权威的来源得到答案,那就好了,正如Noseratio所说的那样。

我认为一个可行的解决scheme可能是使用自定义的awaiter通过OperationContext.Currentstream动新的操作上下文。 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); } } 

这里是FlowingOperationContextScopeContinueOnScope的实现(仅稍微testing过):

 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); } } } 

我决定写我自己的代码,这有助于此,张贴万一这有助于任何人。 似乎有点不会出错(意外的比赛等)与上面的SimpleAwaiter执行,但你是裁判:

 public static class WithOperationContextTaskExtensions { public static ContinueOnOperationContextAwaiter<TResult> WithOperationContext<TResult>(this Task<TResult> @this, bool configureAwait = true) { return new ContinueOnOperationContextAwaiter<TResult>(@this, configureAwait); } public static ContinueOnOperationContextAwaiter WithOperationContext(this Task @this, bool configureAwait = true) { return new ContinueOnOperationContextAwaiter(@this, configureAwait); } public class ContinueOnOperationContextAwaiter : INotifyCompletion { private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter; private OperationContext _operationContext; public ContinueOnOperationContextAwaiter(Task task, bool continueOnCapturedContext = true) { if (task == null) throw new ArgumentNullException("task"); _awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); } public ContinueOnOperationContextAwaiter GetAwaiter() { return this; } public bool IsCompleted { get { return _awaiter.IsCompleted; } } public void OnCompleted(Action continuation) { _operationContext = OperationContext.Current; _awaiter.OnCompleted(continuation); } public void GetResult() { OperationContext.Current = _operationContext; _awaiter.GetResult(); } } public class ContinueOnOperationContextAwaiter<TResult> : INotifyCompletion { private readonly ConfiguredTaskAwaitable<TResult>.ConfiguredTaskAwaiter _awaiter; private OperationContext _operationContext; public ContinueOnOperationContextAwaiter(Task<TResult> task, bool continueOnCapturedContext = true) { if (task == null) throw new ArgumentNullException("task"); _awaiter = task.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); } public ContinueOnOperationContextAwaiter<TResult> GetAwaiter() { return this; } public bool IsCompleted { get { return _awaiter.IsCompleted; } } public void OnCompleted(Action continuation) { _operationContext = OperationContext.Current; _awaiter.OnCompleted(continuation); } public TResult GetResult() { OperationContext.Current = _operationContext; return _awaiter.GetResult(); } } } 

用法(一个小手册和嵌套未经testing…):

  /// <summary> /// Make a call to the service /// </summary> /// <param name="action"></param> /// <param name="endpoint"> </param> public async Task<ResultCallWrapper<TResult>> CallAsync<TResult>(Func<T, Task<TResult>> action, EndpointAddress endpoint) { using (ChannelLifetime<T> channelLifetime = new ChannelLifetime<T>(ConstructChannel(endpoint))) { // OperationContextScope doesn't work with async/await var oldContext = OperationContext.Current; OperationContext.Current = new OperationContext((IContextChannel)channelLifetime.Channel); var result = await action(channelLifetime.Channel) .WithOperationContext(configureAwait: false); HttpResponseMessageProperty incomingMessageProperty = (HttpResponseMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpResponseMessageProperty.Name]; string[] keys = incomingMessageProperty.Headers.AllKeys; var headersOrig = keys.ToDictionary(t => t, t => incomingMessageProperty.Headers[t]); OperationContext.Current = oldContext; return new ResultCallWrapper<TResult>(result, new ReadOnlyDictionary<string, string>(headersOrig)); } } 

简单的方法是移动使用块外的等待

 public Task<Document> GetDocumentAsync(string docId) { var docClient = CreateDocumentServiceClient(); using (new OperationContextScope(docClient.InnerChannel)) { var task = docClient.GetDocumentAsync(docId); } return await task; } 

我有点困惑,我发现这个Blog: 基于任务的WCF中的asynchronous操作

这是一个asynchronous的wcf通信:

 [ServiceContract] public interface IMessage { [OperationContract] Task<string> GetMessages(string msg); } public class MessageService : IMessage { async Task<string> IMessage.GetMessages(string msg) { var task = Task.Factory.StartNew(() => { Thread.Sleep(10000); return "Return from Server : " + msg; }); return await task.ConfigureAwait(false); } } 

客户:

 var client = new Proxy("BasicHttpBinding_IMessage"); var task = Task.Factory.StartNew(() => client.GetMessages("Hello")); var str = await task; 

那么这也是一个好方法?

我遇到了同样的问题,但是,我发现我根本不需要使用async / await。

由于您没有对结果进行后处理,因此无需等待回复。 如果您确实需要处理结果,只需使用旧式的TPL延续。

 public Task<MyDomainModel> GetHomeInfoAsync(DateTime timestamp) { using (var helper = new ServiceHelper<ServiceClient, ServiceContract>()) { return helper.Proxy.GetHomeInfoAsync(timestamp).ContinueWith(antecedent=>processReplay(antecedent.Result)); } } 

我不知道这是否有帮助,但在我的search中看到这个问题来回答同样的问题,我遇到了这个问题 。

由此导致,我认为你的代码应该是这样的:

 public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp) { using (var client = CreateDocumentServiceClient()) { await client.BeginGetHomeInfoAsync(timestamp); } } 

我意识到我的答案来得相当晚:P但它可能会帮助别人。