Raise Events in .Net on the Main UI Thread

Raise Events in .NET on the main UI thread

Your library could check the Target of each delegate in the event's invocation list, and marshal the call to the target thread if that target is ISynchronizeInvoke:

private void RaiseEventOnUIThread(Delegate theEvent, object[] args)
{
foreach (Delegate d in theEvent.GetInvocationList())
{
ISynchronizeInvoke syncer = d.Target as ISynchronizeInvoke;
if (syncer == null)
{
d.DynamicInvoke(args);
}
else
{
syncer.BeginInvoke(d, args); // cleanup omitted
}
}
}

Another approach, which makes the threading contract more explicit, is to require clients of your library to pass in an ISynchronizeInvoke or SynchronizationContext for the thread on which they want you to raise events. This gives users of your library a bit more visibility and control than the "secretly check the delegate target" approach.

In regard to your second question, I would place the thread marshalling stuff within your OnXxx or whatever API the user code calls that could result in an event being raised.

.NET raise event on UI thread

Here's a quick example using SynchronizationContext.

Note that it assumes you are creating the class from the main UI thread, and you store that current context from the constructor:

Public Class Form1

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim d As New Duck("Bob")
AddHandler d.Quack, AddressOf duck_Quack
Label1.Text = "Waiting for Quack..."
d.RaiseThreadedQuack(3)
End Sub

Private Sub duck_Quack(source As Duck)
Label1.Text = "Quack received from: " & source.Name
End Sub

End Class

Public Class Duck

Public ReadOnly Property Name() As String
Private sc As WindowsFormsSynchronizationContext

Public Sub New(ByVal name As String)
Me.Name = name
sc = WindowsFormsSynchronizationContext.Current
End Sub

Public Event Quack(ByVal source As Duck)

Public Sub RaiseThreadedQuack(ByVal delayInSeconds As Integer)
Dim T As New Threading.Thread(AddressOf ThreadedQuack)
T.Start(delayInSeconds)
End Sub

Private Sub ThreadedQuack(ByVal O As Object)
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(O).TotalMilliseconds)

sc.Post(AddressOf RaiseUIQuack, Me)
End Sub

Private Sub RaiseUIQuack(ByVal O As Object)
RaiseEvent Quack(O)
End Sub

End Class

How can I raise an event on the Main UI thread from background task in UWP APP (vb.net)

I've not done any UWP development myself but I believe the correct way to do as you want is to use the SynchronizationContext class, as in WPF. The principle is that the SynchronizationContext.Current property will yield a thread-specific instance of the class, so you can get the value of that property on the UI thread and then use that object elsewhere to marshal a call to the owning thread. The property value is usually retrieved in the constructor of an object that is itself created on the UI thread, e.g.

Imports System.Threading

Public Class SomeClass

'Get the context for the thread on which the current instance is created.
Private uiContext As SynchronizationContext = SynchronizationContext.Current

Private Sub ThisMethodIsCalledOnASecondaryThread()
uiContext.Post(AddressOf ThisMethodIsCalledOnTheUIThread, Nothing)
End Sub

Private Sub ThisMethodIsCalledOnTheUIThread(data As Object)
'Execute UI thread logic here, e.g. raise an event.
End Sub

End Class

raise event in UI thread

I use MethodInvoker, which has to be declared (because it does not exist under the compact framework), then include a link to the parent control (usually the main form):

class Publisher {
#if PocketPC // MethodInvoker delegate is not declared in CF!
public delegate void MethodInvoker();
#endif
private Form _parent;

public Publisher(Form parent) {
_parent = parent;
}

public event EventHandler OnEventHandler;
private void OnEvent() {
if (OnEventHandler != null) {
MethodInvoker mi = delegate { OnEventHandler(this, EventArgs.Empty); };
try {
_parent.BeginInvoke(methInvoker);
} catch (ObjectDisposedException) {
} catch (NullReferenceException err) {
LogError("OnEvent NullReference", err);
} catch (InvalidOperationException err) {
LogError("OnEvent InvalidOperation", err);
}
}
}
}

Hopefully, you are able to do something with that. ;)

~Joe

C# Properly raise event from background thread and handle it in the UI thread

Try to use BackgroundWorker. You'll be able to update your UI in RunWorkerCompleted event.

Is this the proper way to inform the main thread of an event from a Background thread?

You write in the comments:

I was under the impression that because the instance was created on the main thread, the event would be called there too.

Objects in C# and .NET are not thread-affine by default. You need to implement this behavior manually. Creating a generic object on a thread doesn't cause that object to raise events on the thread the object was created on. Events are raised on the caller thread, if you don't provide a custom implementation that changes this.

With your current code, the StatusMessageEvent events will be raised on the caller thread (the one that runs the StartListening method).

If you have a GUI application (e.g. WPF, WinForms), you can manually marshal to the main thread when necessary.

class Program
{
static SynchronizationContext mainThreadContext;

static void Main()
{
// app initialization code here

// you could also do this somewhere in your main window
mainThreadContext = SynchronizationContext.Current;
}

static void MessengerEvent(object sender, EventArgs<string> e)
{
// do additional stuff here that can be done on a background thread

// The UpdateUI method will be executed on the UI thread
mainThreadContext.Post(_ => UpdateUI(), null);
}

static void UpdateUI() { }
}

Instead of the SynchronizationContext, you can use Control.BeginInvoke (WinForms) or Dispatcher.BeginInvoke (WPF).

If you have a console app and for some reason need to marshal to the main thread... Well, then you need to implement your own SynchronizationContext and something like a task scheduler or a dispatcher or a main loop. That's not easy.



Related Topics



Leave a reply



Submit