セッション、コールバック、および TPL を使用してどこまで実行できるかを理解するために、いくつかの WCF 実験を行っています。これらの実験の 1 つに奇妙な結果があります... サービスは双方向バインディング (NetTcpBinding) で実装され、通知はコールバックとして実装されます。予想される動作は次のとおりです。
- クライアントは同期方法でサービス (Do) のメソッドを呼び出します
- サービスは同期方法でクライアントのコールバックを呼び出します
- クライアント (コールバック内) は、非同期の方法でサービスのメソッドを 2 番目に呼び出します。
サービスにはセッションが必要です。InstanceContext は PerSession で、同時実行モードは Single です。この状況では、コールバックのコンテキストでは、(同じクライアント上の) サービスのメソッドを同期的に呼び出すことはできないため、コードは次のようになります。
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(INotificationCallback))]
public interface INotificationService
{
[OperationContract]
int Do(int value);
[OperationContract]
int ReDo(int value);
}
public interface INotificationCallback
{
[OperationContract(IsOneWay = true)]
void Notify(int value);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class NotificationService : INotificationService
{
public int Do(int value)
{
var notificationCallback = OperationContext.Current.GetCallbackChannel<INotificationCallback>();
//Task.Run(() => notificationCallback.Notify(value * 2));
notificationCallback.Notify(value * 2);
Thread.Sleep(1000);
return value * 2;
}
public int ReDo(int value)
{
return value + 1;
}
}
クライアントでのコールバック インターフェイスの実装:
class CallbackInstance : INotificationServiceCallback
{
public NotificationServiceClient Client { get; set; }
#region Implementation of INotificationServiceCallback
public void Notify(int value)
{
Console.WriteLine("Notified: {0}", value);
Task<int> task = Client.ReDoAsync(value);
task.ContinueWith(t => Console.WriteLine("In Notify - ReDo({0}): {1}", value, t.Result));
}
#endregion
}
すべてのコードを実行するメイン メソッド:
static void Main(string[] args)
{
CallbackInstance callback = new CallbackInstance();
NotificationServiceClient client = new NotificationServiceClient(new InstanceContext(callback));
callback.Client = client;
int result = client.Do(10);
Console.WriteLine("Do({0}): {1}", 10, result);
Console.ReadLine();
}
私の考えでは、Notify メソッドの実装内の行は次のとおりです。
Task<int> task = Client.ReDoAsync(value);
task.ContinueWith(t => Console.WriteLine("In Notify - ReDo({0}): {1}", value, t.Result));
デッドロックを解除する必要がありますが...いいえ、ReDoAsyncが呼び出されると、「現在のメッセージの処理が完了するまで応答を受信できないため、この操作はデッドロックになる...」という例外がスローされます。はい、知っています。実際、問題を回避するためにサービスを非同期でコールバックしようと考えましたが、機能していません。
しかし、私を夢中にさせるのは、コードを少し変更するだけで魔法のように機能するという事実です。私が試した ReDoAsync 呼び出しの代わりに:
Task<int> task = Task.Run(() => Client.ReDo(value));
そしてそれは機能します!問題は、クライアント メソッドの非同期バージョンは、機能するものと同一または類似の呼び出しを行うことになっているのではないかということです。そうでない場合、クライアント メソッドの非同期バージョンは実際に何をするのでしょうか?
ありがとう。