从不同线程调用时,WCF Duplex 回调方法从不执行

WCF Duplex callback method never executes when invoked from a different thread

好吧,我对这件事束手无策。我有一个 WCF 双工服务。以下是架构的工作原理:

  1. 客户端打开到端点的连接并提供回调实现
  2. 该服务接受该请求并在其他线程上做一些事情(可能是 1 秒也可能是 2 分钟,这就是我不使用异步操作的原因)
  3. 处理完成后调用客户端的回调

问题是当服务调用那个回调时,似乎没有任何反应。没有错误,什么都没有。经过进一步调查,我在服务器跟踪中发现了一个异常:

The I/O operation has been aborted because of either a thread exit or an application request

这发生在尝试执行回调之后。

客户端从未收到响应或关闭。所发生的一切是,由于初始请求是在与主线程不同的线程上发出的,它只是永远等待该线程完成。

最奇怪的是,如果我尝试在客户端调用的操作中调用回调,而不进入另一个线程,一切正常 - 回调被成功调用,这让我相信我已正确配置服务,但遇到 threading/deadlocking 问题。

我是这样调用服务的:

SubmissionServiceClient client = CreateClientInstance();
        client.Open();
        Guid executionId = await client.SubmitAsync(submission);
        submissionCompletionSource.Task.Wait(); //waits for the callback to be called (I omitted the extra wiring code for better readability)
        client.Close();

private SubmissionServiceClient CreateClientInstance()
{
    NetHttpBinding binding = new NetHttpBinding();
    binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always;
    EndpointAddress endpointAddress = new EndpointAddress("ws://localhost:9080/SubmissionRouter");
    InstanceContext instanceContext = new InstanceContext(this);
    SubmissionServiceClient submissionServiceClient = new SubmissionServiceClient(instanceContext,binding,endpointAddress);
    return submissionServiceClient;
}

这是回调操作:

public void SubmissionProcessed(SubmissionResultDto result)
        {
            submissionCompletionSource.TrySetResult(result);
        }

这是客户端调用的服务操作:

public Guid Submit(SubmissionDto submission, ISubmissionCallback callback)
        {
            ExecutionDto execution = new ExecutionDto()
            {
                Id = Guid.NewGuid(),
                Submission = submission
            };
            RequestExecution(execution); //Queues the execution of the operation
            submissions.Add(execution.Id, callback);

            return execution.Id;
        }

这是服务调用客户端回调的地方(此方法在与初始请求发出的线程不同的线程上执行):

                ISubmissionCallback callback = submissions[submissionResult.ExecutionId];
                    callback.SubmissionProcessed(submissionResult);

服务行为:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]

正如您在提交时看到的那样,该服务将回调存储在字典中,并与一个 id 配对,稍后它会使用它来检索回调并调用它。我相信它失败的原因是因为我试图在不同的线程上执行此操作。

编辑: 我在服务中添加了一个 Ping 操作,它调用另一个线程,该线程挂起 3 秒,然后在客户端调用 Pong 函数。

public void Ping()
        {
            var callback = OperationContext.Current.GetCallbackChannel<ISubmissionCallback>();
            Task.Run(() =>
            {
                System.Threading.Thread.Sleep(3000);
                callback.Pong();
            });
        }

客户: class 程序 { 静态无效主要(字符串[]参数) { NetHttpBinding 绑定 = new NetHttpBinding(); binding.WebSocketSettings.TransportUsage = WebSocketTransportUsage.Always; EndpointAddress endpointAddress = new EndpointAddress("ws://localhost:9080/SubmissionRouter"); InstanceContext instanceContext = new InstanceContext(new Callback()); SubmissionServiceClient submissionServiceClient = new SubmissionServiceClient(instanceContext, binding, endpointAddress);

        submissionServiceClient.Ping();

        Console.Read();
    }

    public void SubmissionProcessed(SubmissionResultDto result)
    {
        throw new NotImplementedException();
    }
    class Callback : ISubmissionServiceCallback
    {
        public void Pong()
        {
            Console.WriteLine("Pong!");
        }

        public void SubmissionProcessed(SubmissionResultDto result)
        {

        }
    }
}

这实际上成功了。我设法在客户端收到了我的答复。我现在正式完全迷路了。

如果您使用 submissionCompletionSource.Task.Wait(); 阻塞 UI 线程,这会导致死锁。默认情况下,WCF 回调发生在 UI 线程上,您可以使用 CallbackBehaviorAttribute

更改行为
[CallbackBehaviorAttribute(UseSynchronizationContext=false)]
class Callback : ISubmissionServiceCallback
{
    public void Pong()
    {
        Console.WriteLine("Pong!");
    }

    public void SubmissionProcessed(SubmissionResultDto result)
    {

    }
}

或者不阻塞 UI 线程。

SubmissionServiceClient client = CreateClientInstance();
client.Open();
Guid executionId = await client.SubmitAsync(submission);
await submissionCompletionSource.Task; //awaits for the callback to be called without blocking the UI.
client.Close();