0

I am (for reasons) forced to perform a long-running operation on the UI thread of my xamarin forms (NetStandard) application. During this operation, I would like to update the UI to give the user feedback on progress. I can make changes to the UI, but the UI will not redraw as I cannot return the instruction pointer to the OS by using await/async.

Is there any way I can process messages on the UI thread without using async/await. In the old Win32 days I think Translate/DispatchMessage could be used to keep the message pump going in long-running operations, and this is similar to what I need.

Background: I have a interface defined and called by the android OS that runs on my UI thread. It is important that we return the result of this long-running operation from this service. Because the interface is not defined as async I cannot use await to let the IP return to the OS. If I did the function would immediately return (without the results being fetched) and continue executing (ie, it would fail). I can't change these circumstances.

// The following class is called directly by the AndroidOS
class CloudHostCardService : HostApduService
{
    // The following method is called by the AndroidOS on the UI thread
    public override byte[] ProcessCommandApdu(byte[] commandApdu, Bundle extras)
    {
        ViewModels.MainPageViewModel.SetStatus("Processing Cmd");  // <-- updates UI
        return processor.ProcessCommand(commandApdu); // <-- Long running operation
    }
}

Is there any other way to trigger a redraw (pump messages in Win32 speak)?

FrozenKiwi
  • 1,362
  • 13
  • 26
  • Are you using Forms or a native Android app? Via Forms, use `BeginInvokeOnMainThread` Or natively: https://stackoverflow.com/questions/36619293/xamarin-begininvokeonmainthread-without-xamarin-forms/36619574#36619574 – SushiHangover Nov 03 '18 at 12:32
  • Sorry, maybe the question wasn't clear. I'm already on the main thread, and am forced to complete a long-running operation there. I can manipulate UI elements, but the system won't redraw because I am holding the instruction pointer for that thread – FrozenKiwi Nov 03 '18 at 15:42
  • Do your "compute" processing on another thread. (As a side note, why can you not add `async` to the callback method?) – SushiHangover Nov 03 '18 at 15:52
  • I can't do either of those things, because the callback is called by the Android OS (in response to an NFC trigger), and the callback is not defined as async. – FrozenKiwi Nov 03 '18 at 16:01
  • 1
    You can "just" add `async` to the method. You should update your question with the code in question, also you can perform a `Task.Run` in that method, that processes what you need on a background thread and then do a RunOnUiThread to update the ui – SushiHangover Nov 03 '18 at 16:03
  • Sorry if I wasn't clear. I am implementing an interface defined by the OS. You cannot call an async function from synchronous code. I cannot modify this function to be async (If I did, the calling code would immediately continue execution if I 'await'ed, which is an error) – FrozenKiwi Nov 03 '18 at 16:10
  • Yes, I can define the method as async, but all that does not change the underlying call to the function, which is not async - and will not 'await' my call. I cannot return the IP to the OS this way: https://stackoverflow.com/questions/35817558/why-does-c-sharp-allow-making-an-override-async – FrozenKiwi Nov 03 '18 at 16:21
  • To clarify: I need to return the result of the long running op. Because the AndroidOS does not 'await' the call to this interface, I have to block the thread to do the fetch in order to return the result. Similar to any time when a synchronous method calls an async method – FrozenKiwi Nov 03 '18 at 18:06
  • I see you added a code sample and you are using HostApduService, see my answer – SushiHangover Nov 03 '18 at 21:39

1 Answers1

1

HostApduService allows you to return null from ProcessCommandApdu and then later use SendResponseApdu to supply the ResponseApdu.

This method is running on the main thread of your application. If you cannot return a response APDU immediately, return null and use the sendResponseApdu(byte[]) method later.

Example:

public class SampleHostApduService : HostApduService
{
    public override void OnDeactivated([GeneratedEnum] DeactivationReason reason)
    {
    }

    public override byte[] ProcessCommandApdu(byte[] commandApdu, Bundle extras)
    {
        // Update your UI via your viewmodel.

        // Fire and forget
        Task.Run(() => {
            this.SendResponseApdu(processor.ProcessCommand(commandApdu));
         });

        // Return null as we handle this using SendResponseApdu
        return null;
    }
}
Community
  • 1
  • 1
SushiHangover
  • 73,120
  • 10
  • 106
  • 165