1

I'm trying to set up a simple duplex service whereby clients connect to the server. Any connected client may execute the BookAdded service operation. When this occurs the server is supposed to raise a callback on all connected clients to notify them of the change.

The callback seems to be working fine except that the callback operation needs to run something on the UI thread using Dispatcher.BeginInvoke.

In my case Console.WriteLine("Callback thread") gets executed buy Console.WriteLine("Dispatcher thread") does not. What is the reason for this?

My service contract:

public interface ICallback
{
    [OperationContract(IsOneWay = true)]
    void BookAdded(string bookId);
}

[ServiceContract(
    CallbackContract = typeof(ICallback), 
    SessionMode = SessionMode.Required)]
public interface IService
{
    [OperationContract]
    bool BookAdded(string bookId);
}

My service implementation:

[ServiceBehavior(
    UseSynchronizationContext = false,
    InstanceContextMode = InstanceContextMode.PerSession, 
    ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class MyService : IService
{
    public bool BookAdded(string bookId)
    {
        try
        {
            Console.WriteLine("Client Added Book " + bookId);

            foreach (ICallback callback in connectedClients)
            {
                callback.BookAdded(bookId);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        return true;
    }
}

My client implementation:

[CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyCallBack: ICallback, IDisposable
{
    private Dispatcher theDispatcher;
    private InstanceContext context;
    private WcfDuplexProxy<IService> proxy;

    [ImportingConstructor]
    public MyCallBack()
    {  
        theDispatcher = Dispatcher.CurrentDispatcher;
        context = new InstanceContext(this);
        proxy = new WcfDuplexProxy<IService>(context);

        proxy.Connect();
    }

    public IService Service
    {
        get
        {
            return proxy.ServiceChannel;
        }
    }

    public void CallServiceOperation()
    {
        Service.BookAdded("BOOK1234");
    }

    public void BookAdded(string bookId)
    {
        Console.WriteLine("Callback thread");
        theDispatcher.BeginInvoke(new Action(() => { Console.WriteLine("Dispatcher thread"); }));
    }

    public void Dispose()
    {
        Service.Disconnect();
        proxy.Close();
    }
djskinner
  • 8,035
  • 4
  • 49
  • 72

2 Answers2

3

What I suspect is happening is that the UI thread is still blocked on the original call to the server witch hasn't finished yet. I think if you change the concurrencymode on the client to reentrant it could work. . What you need to do is set [CallbackBehavior(UseSynchronizationContext = false)] on the callback.

This article explains this issue quite well.

GJ

gjvdkamp
  • 9,929
  • 3
  • 38
  • 46
  • I actually based my code on that article. I've now simplified the situation back to the original article: [ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)] [CallbackBehavior(UseSynchronizationContext = false, ConcurrencyMode = ConcurrencyMode.Single)] The the problem still persists. Even if the UI thread was locked, doesn't Dispatcher.BeginInvoke queue the delegate and it will run once the UI thread becomes available? There is no deadlock on the UI thread because the UI continues to work as normal. – djskinner Apr 04 '11 at 10:27
  • (sorry didn;t read the example very well) What happens if you wrap Console.Writeline in it's own method and call that with the action? Set a breakpoint in there. That way you can see if it fails in calling the method or in wrting to console. Also set VS to break on CLR exceptions so it'll stop. – gjvdkamp Apr 04 '11 at 11:10
  • The breakpoint doesn't get hit nor are any CLR exceptions thrown it seems. – djskinner Apr 04 '11 at 11:39
  • Ok so that would narrow it down to theDispatcher.BeginInvoke... what if you chnage that to Dispatcher.CurrentDispatcher.BeginInvoke? or create the action on a seperate line and then call it? Just to narrow it down some more. – gjvdkamp Apr 04 '11 at 11:42
  • That make no difference either! This is really strange. – djskinner Apr 04 '11 at 12:04
  • Could you update the post with your current code? Does seem strange indeed. So the action does not get called? – gjvdkamp Apr 04 '11 at 12:08
  • This is what I get when I step through with the debugger: [...] Step into: Stepping over method without symbols 'System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete' Step into: Stepping over method without symbols 'System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame' Step into: Stepping over method without symbols 'System.Runtime.AsyncResult.Complete' – djskinner Apr 04 '11 at 12:08
  • Are you doing this in debug mode of release? – gjvdkamp Apr 04 '11 at 12:16
  • Hmm, I've just tried using: SynchronizationContext.Current.Post(new SendOrPostCallback((x) => UpdateUI()), new object { }); Now the *server* faults with: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '10675199.02:48:05.4775807'. – djskinner Apr 04 '11 at 12:25
  • Ok, I've partially solved it. I didn't realise I was actually constructing MyCallBack (the client) in a BackgroundWorker thread. If I don't do this then it works! But that still doesn't explain to me why the Dispatcher doesn't invoke the code. Any ideas? Ideally I would like to construct the client in a background thread. – djskinner Apr 04 '11 at 12:36
  • Hi sorry no, this is where my knowledge ends. You could post this in a ms forum, then you can get the guys who wrote WCF to look at this. – gjvdkamp Apr 04 '11 at 12:54
0

I think I have encountered similar issue with yours. I resolved it with a new thread to invoke Dispatcher.beginInvoke. As I understand, the UI thread sends request to Service, the Service will invoke callback contract that implements in your client during the service operation. So if in callback operation it invoke a control of UI thread which is pending to wait response from Service operation. This is why the client is deadlocked. So you can try code below: Thread th=new Thread(new ThreadStart(()=>{Console.WriteLine("Callback thread");
theDispatcher.BeginInvoke(new Action(() => { Console.WriteLine("Dispatcher thread"); }));
})); th.Start();

Paul Zhou
  • 193
  • 2
  • 12