0

Im really stuck here... I have a XAML Page UI and want to call an async function everytime the user interacts with the UI.
I use SignalR for networking:

public static class ProtocolClient
{
    private static HubConnection hubConnection;
    private static IHubProxy protocolHubProxy;

    public static async void connect(string server)
    {
        hubConnection = new HubConnection(server);
        protocolHubProxy = hubConnection.CreateHubProxy("ProtocolHub");
        protocolHubProxy.On<Body>("BodiesChanged", body =>
            //call a callback to return body
        );
        await hubConnection.Start(); //wait for connection
    }

    public static async void sendTouch(Touch touch)
    {
        Body body = await protocolHubProxy.Invoke<Body>("GetBodyForTouch", touch);
        //call a callback to return body
    }
}

UI:

public sealed partial class MainPage : Page
{
    [...]
    private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        [...]
        switch (ptrPt.PointerDevice.PointerDeviceType)
        {
            case Windows.Devices.Input.PointerDeviceType.Mouse:
                if (ptrPt.Properties.IsLeftButtonPressed)
                {
                    //call sendTouch
                }
                break;
            default:
                break;
        }
        [...]
    }
}

I need a callback which can modify the UI. How can I call connect and sendTouch out of the UI and pass them a callback?

pythonimus
  • 293
  • 4
  • 15

2 Answers2

0

You don't need a callback. Just add the code after the await hubConnection.Start(); statement. Your method is 'cut in multiple methods' and will 'continue' after the await comes back. The await works like a blocking statement, but will not freeze the gui.

public static async void connect(string server)
{
    hubConnection = new HubConnection(server);
    protocolHubProxy = hubConnection.CreateHubProxy("ProtocolHub");
    protocolHubProxy.On<Body>("BodiesChanged", body =>
        //call a callback to return body
    );
    await hubConnection.Start(); //wait for connection

    // add code here.
}

When handling commands async (from gui events), don't forget to disable controls to prevent executing the command more than ones.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • Thanks, but how do I pass the body object from .On to the UI? These functions are in different classes. – pythonimus Jul 11 '16 at 12:13
  • Never worked with `SignalR`, but I assume that the `Start()` method is already returned when the `body` is called whenever a message arrives. So there is a misconception about where to handle the received messages. You don't need to 'move' the objects from the `On` event after the `await hubConnection.Start()`. Just handle the message there. – Jeroen van Langen Jul 11 '16 at 12:23
  • You are right. BUT class ProtocolClient does not and should not have anything to do with UI elements. – pythonimus Jul 11 '16 at 12:26
0

Don't use async void methods. If you don't need to return a value, use async Task - if you do, use async Task<SomeType>.

Then, when you need to call an async method (and by convention, these should be named like ConnectAsync and SendTouchAsync), await it:

await SendTouchAsync(...);

When the asynchronous workflow ends, your continuation will be marshalled back to the UI thread (because you awaited from within a synchronization context), and you can manipulate the UI easily.

await kind of appears to work when you use async void, but the problem is that the caller has no way of tracking the asynchronous workflow - as far as the caller is concerned, the method just ended right then and now, and the code in the caller continues as usual.

Make sure to mark Canvas_PointerPressed as async too - sadly, in this case, it must be async void. Make sure to never call the event handler directly - the UI thread can handle the callbacks correctly, your code can't. If you need the same logic from other methods, just separate it into a proper async Task method and await that from the event handler.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Thanks, but I still have no clue on how to get the body Object from On somehow to the caller class MainPage. – pythonimus Jul 11 '16 at 14:41
  • @pythonimus Just use the return value. Change the signature to `async Task SendTouchAsync(...)`, and just do `return body;` inside. The `await SendTouchAsync(...);` has your return value. – Luaan Jul 11 '16 at 16:00
  • Thanks, but my question was how to get the body object from the [On function](https://msdn.microsoft.com/en-us/library/dn433683(v=vs.118).aspx) to the UI. This part: `protocolHubProxy.On("BodiesChanged", body => //call a callback to return body);` How to create this Action callback and pass it to the connect function, so that the callback can modify UI elements? – pythonimus Jul 11 '16 at 21:47
  • @pythonimus Ah, okay. In that case, this is a 100% duplicate of the quite common "how do I modify the UI from another thread" question :) – Luaan Jul 12 '16 at 08:01