タスクベースの操作でプロキシを生成しました。
async/await を使用して、このサービスを適切に呼び出すにはどうすればよいですか (ServiceClient
および を後で破棄します)。OperationContext
私の最初の試みは:
public async Task<HomeInfo> GetHomeInfoAsync(DateTime timestamp)
{
using (var helper = new ServiceHelper<ServiceClient, ServiceContract>())
{
return await helper.Proxy.GetHomeInfoAsync(timestamp);
}
}
と を作成し、後でそれらを破棄するクラス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;
}
ただし、2 つのサービスを同時に呼び出すと、「この OperationContextScope は、作成されたものとは異なるスレッドで破棄されています」というエラーが表示されて、惨めに失敗しました。
MSDNは次のように述べています。
OperationContextScope ブロック内で非同期の「待機」パターンを使用しないでください。継続が発生すると、別のスレッドで実行される可能性があり、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);
}
}
彼のコードに関する私の問題は、彼が ServiceClient で Close (または Abort) を呼び出さないことです。
custom を使用してを伝播する方法も見つけました。しかし、それが多くの「危険な」コードであるという事実に加えて、彼は次のように述べています。OperationContextScope
SynchronizationContext
操作コンテキストスコープの破棄に関していくつかの小さな問題があることに注意する価値があります (呼び出しスレッドでのみ破棄できるため) が、これは問題ではないようです (少なくともDispose() を実装しますが、Finalize() は実装しません。
それで、私たちはここで運が悪いのですか?async/await を使用して WCF サービスを呼び出し、ServiceClient
との両方を破棄するための実証済みのパターンはありOperationContextScope
ますか? たぶん、Microsoft の誰か (おそらく教祖 Stephen Toub :)) が助けてくれるでしょう。
ありがとう!
[アップデート]
ユーザー Noseratio の多くの助けを借りて、機能するものを思いつきました: 使用しないでくださいOperationContextScope
。これらの理由のいずれかで使用している場合は、シナリオに合った回避策を見つけてください。それ以外の場合、本当に必要な場合は、それをキャプチャするOperationContextScope
a の実装を考え出す必要がありますが、それは非常に難しいようです(可能であれば、これがデフォルトの動作ではない理由があるはずです)。 )。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;
}
}
}
}
このクラスは拡張機能をサポートしていることに注意してください。おそらく、資格情報を継承して提供する必要があります。
唯一の可能性のある「落とし穴」は、 では、プロキシから取得した をGetHomeInfoAsync
返すことができないというTask
ことです (これは当然のことのように思えますが、Task
既にあるのに新しいものを作成する必要があります)。この場合await
、プロキシTask
を使用してからを閉じる (または中止する)ServiceClient
必要があります。そうしないと、サービスを呼び出した直後に (バイトがネットワーク経由で送信されている間に) 閉じることになります。
OK、それを機能させる方法はありますが、Noseratio が述べているように、信頼できる情報源から回答を得られるとよいでしょう。