VinDag
VinDag

Reputation: 65

BeginInvoke behavior different in WPF and WinForms

I have an application I am working on in WPF. It uses a serial port connection. I have created a class that wraps the serial port connection and manages buffers, etc.

I developed it when writing a C# WinForms application (.NET6). To allow the serial port events to update a textbox on the main form and send received bytes to a packet handler, I created a delegate and used BeginInvoke to run it. It worked just fine.

I took the same wrapper and included it in a new WPF application, without the textbox updates/packet handler included just to get started (so, only sending bytes out). It also ran fine. I uncommented the receive part of the wrapper, and all still compiles fine (but of course without code on the main form the incoming bytes were not being processed).

When I added the code to the main form to invoke the delegate, I got compile errors. At first, I thought it was a .NET6/.NET8 thing, but I opened the old project and changed the target .NET and it still ran fine.

So, I'm wondering if it's a WinForms vs. WPF thing. The error is:

CS1501 No overload for method 'BeginInvoke' takes 3 arguments

Yet, the exact same code compiles and runs in the old project.

Here is the code in question:

    void ProcessRecievedByte_Callback(object sender, byte rcvdByte)
    {
        this.BeginInvoke(new ES_UART.RxBufferRemoveByteEventHandler(ES_RxBufferProcessor), sender, rcvdByte);
    }

    void ES_RxBufferProcessor(object sender, byte e)
    {
       switch (receiveState)
        {
            case receive_state.WAITING_FOR_SOP:
                if (e == '$')
                {
                    receiveState = receive_state.IN_A_PACKET;
                    packetIndex = 0;
                    packetBuffer[packetIndex] = e;
                    packetIndex++;
                }
                break;
            case receive_state.IN_A_PACKET:
                if (packetIndex < MAX_PACKET_SIZE)
                {
                    packetBuffer[packetIndex] = e;
                    packetIndex++;
                }
                if (e == '\n')
                    receiveState = receive_state.PACKET_RECIEVED;
                break;
            case receive_state.PACKET_RECIEVED:
                break;
        }
    }

Upvotes: 1

Views: 219

Answers (2)

Emperor Eto
Emperor Eto

Reputation: 3518

TL;DR - This is simply due to differences between WPF and WinForms. Despite sharing the name BeginInvoke, these methods are not actually the same, but they indeed do effectively the same thing in their respective platforms.

Background

I understand from reading your question that ProcessRecievedByte_Callback will be invoked by the serial port hardware, and thus will execute on a background thread. Further, while you don't show the actual code that updates the TextBox ("without the textbox updates/packet handler included just to get started"), I'm assuming you will in fact be updating the UI in the ES_RxBufferProcessor callback as you say.

Given those assumptions, you indeed do need to use some mechanism to ensure ES_RxBufferProcessor (or at least the portion of it that updates the UI) is executed on the UI thread. While there are multiple ways to do this, I'll start with the method you ask about - BeginInvoke - before addressing the others.

BeginInvoke

As you probably already know, WinForms and WPF are both single-threaded user interfaces; by design the UI can only be manipulated by code executing on the UI thread. However, there are many times - including when your code is being invoked by a hardware callback - when you find yourself on a background thread but needing to execute code on the UI thread. That's where BeginInvoke and its progeny enter.

WinForm's method - Control.BeginInvoke - and WPF's method - Dispatcher.BeginInvoke - both serve the same purpose in their respective platforms. They are not the exact same method, though, because the means of posting code to a single UI thread is an implementation detail of the platform and not intrinsic to .NET.

Note that there is yet a third BeginInvoke method in .NET Framework that is no longer supported in modern .NET - Delegate.BeginInvoke. This was a means of executing code on a background thread (the opposite of WinForms and WPF's BeginInvoke) and has indeed been obviated by the async/await pattern. This is not what you appear to be trying (or want) to do, but I'm pointing it out for completeness.

In your specific case, if you want to use BeginInvoke to execute on the UI thread, all you have to do is adapt the call slightly to use Dispatcher.BeginInvoke rather than this.BeginInvoke:

    void ProcessRecievedByte_Callback(object sender, byte rcvdByte)
    {
        this.Dispatcher.BeginInvoke(
            new ES_UART.RxBufferRemoveByteEventHandler(ES_RxBufferProcessor), 
            sender, 
            rcvdByte);
    }

(Sidenote - I'm assuming you're inside a WPF UserControl here. Consequently, there would be no this.BeginInvoke method available - the method would be this.Dispatcher.BeginInvoke, and it would actually have the same method signature as the WinForms variant. I'm not sure exactly why you're getting a CS1501 error without seeing all your code, but as long as you call Dispatcher's BeginInvoke method, the above will work).

And that's basically it. Now code running inside ES_RxBufferProcessor can safely update the UI thread.

The newer approach - InvokeAsync

Before async/await, asynchronous .NET methods often used the BeginXXX and EndXXX pattern. I won't get into all the details as this answer is already too long, but async/await did indeed largely obviate the need for this pattern. The methods aren't deprecated, but they really aren't used much anymore.

Instead, Dispatcher adds InvokeAsync which, among its many overloads, takes an Action and returns an awaitable object so you can wait for the delegate to finish if you want to. If you don't await it, it does the same thing as BeginInvoke except you give it a parameterless lambda/Action instead of a delegate:

    void ProcessRecievedByte_Callback(object sender, byte rcvdByte)
    {
        // Await or not, your choice
        _ = this.Dispatcher.InvokeAsync(() =>
        {
            ES_RxBufferProcessor(sender, rcvdByte);
        });
    }

But doesn't async/await eliminate the need for any of this?

Some people mistakenly believe the introduction of async/await removed the need to use the Dispatcher when calling UI code from a worker thread, but this is not the case. What async/await does do is make it simpler to wait for the dispatched code to complete, since async/await is pretty much universally considered superior to the BeginXXX/EndXXX pattern. So again, you could argue the pre-async/await BeginInvoke is obsolete now, and if I were reviewing new code I'd certainly discourage its use in favor of InvokeAsync unless a compelling reason were given.

It's also true that in some cases - maybe even in your specific case - there may be ways to rearchitect the code to avoid finding yourself on a worker thread in the first place, which is what the other answer proposes. This is not always possible, especially when low level hardware is concerned. But if you can rearrange the problem so that you never land on a worker thread, then you indeed don't need to use the Dispatcher. This is because even if you await code that itself runs on a worker thread, as long as you make the call from the UI thread, you will: (a) not block the UI thread while waiting for the async code; and (b) return on the UI thread when the awaited code completes, at which point you can perform any UI updates you need. Under the hood this still in fact uses Dispatcher, but it does so transparently.

Some people may dislike my answer because they believe there is a better approach to your problem all together - and they may well be right - but I'm choosing to answer the question asked (and am fine if others disagree). What is a fact, though, is that if for whatever reason you find your code running on a worker thread and needing to do something on the UI thread, then you need to use the Dispatcher, directly or indirectly.

Progress - The Platform-Agnostic Option

Finally, the comment discussion brought up using IProgress<T> instead of the Dispatcher, but didn't give a clear explanation of how to use it here, so let me finish with that.

IProgress<T> is just a barebones interface for progress reporting. You can't do anything with it on its own - you need to either implement it yourself (which would require you to use the Dispatcher), or use .NET's built-in implementation - Progress<T>.

Here's how you might use it in your case:

  1. Declare an instance of Progress<Action> in a widely-accessible location (perhaps a public static member of App) and - and this part is critical - construct it while on the UI thread. This can be when your application starts. Declare it like this:

    public static IProgress<Action> MyDispatcherAbstraction { get; private set; }
    

    (For some reason, Progress<T> implements Report explicitly, so you need the interface, rather than the class instance, to actually use the Report method. Don't ask me why.) Then construct it, possibly in Application.OnStartup:

    MyDispatcherAbstraction = new Progress<Action>(action => action()); 
    

    The Action given to the constructor gets executed with the T provided to IProgress<T>.Report. Hence, by making the generic type another Action, you can get it to simply execute whatever code you want and it will do so on the thread on which it was created - which is why you must create it while on the UI thread if you want to use it in place of Dispatcher.

  2. In your ProcessRecievedByte_Callback:

     void ProcessRecievedByte_Callback(object sender, byte rcvdByte)
     {
         MyDispatcherAbstraction.Report(() =>
         {
             ES_RxBufferProcessor(sender, rcvdByte);
         });
     }
    

Just realize that, under the hood, this actually winds up calling Dispatcher.BeginInvoke anyway. (See DispatcherSynchronizationContext and Progress source). Specifically the IProgress<T>.Report explicit implementation calls the Post method on the synchronization context. So provided you were on the UI thread when it was instantiated, the sync context will be DispatcherSynchronizationContext, and DispatcherSynchronizationContext.Post calls Dispatcher.BeginInvoke. In WPF apps, all paths to the UI run through the Dispatcher one way or another!

Also, FYI, the singleton pattern is fine because the built-in Progress<T> implementation is inherently thread safe, as all it does is invoke the delegate on the thread synchronization context on which the Progress<T> object was instantiated. (See also here). Literally it's the same as what you were trying to do with BeginInvoke, just abstracting away the platform-specific details from you.

So, is Progress<T> better than using Dispatcher.InvokeAsync? Well, the upside is that it's platform agnostic since it's a fundamental .NET type not dependent on WPF. On the other hand, unlike InvokeAsync, IProgress<T>.Report gives you no way to wait for the completion of the delegate before returning. Now in your case, since you were using BeginInvoke anyway, you already didn't really seem to care about this, but it may matter in other cases. But it would be inaccurate to think that it's a replacement for using the Dispatcher in all cases.

As a matter of personal taste, I find the Progress<T> solution to be a little bit of a hack. For a genuine asynchronous progress reporting mechanism it works well enough, but using it to execute arbitrary code on the UI thread on demand isn't exactly its intended purpose. Plus, if you aren't careful to construct it on the UI thread, the entire scheme fails, whereas by using Dispatcher you have no fear of a colleague or future you making such a mistake.

Conclusion

Both WinForms and WPF have dedicated UI threads and thus need, and have, a dispatching mechanism. Neither platform could function without one, and the introduction of async/await does not obviate this need. WPF however offers other newer ways of invoking code on the UI thread, while .NET itself offers a platform-agnostic (but unawaitable) option in the form of Progress<T>.

Upvotes: 0

Clemens
Clemens

Reputation: 128116

You do not need any BeginInvoke call. Just create a StremReader over the BaseStream of the SerialPort and call and await the ReadLineAsync method in a loop.

Here is a small example that reads NMEA messages from a GPS device on a virtual COM port and shows them in a TextBlock (declared in XAML):

public MainWindow()
{
    InitializeComponent();

    Loaded += OnWindowLoaded;
}

public async void OnWindowLoaded(object sender, RoutedEventArgs args)
{
    using var port = new SerialPort("COM6", 9600);
    port.Open();

    using var reader = new StreamReader(port.BaseStream);

    string message;

    while (!string.IsNullOrEmpty(message = await reader.ReadLineAsync()))
    {
        textBlock.Text = message;
    }
}

Upvotes: 0

Related Questions