C
C#2y ago
.logik.

Application called an interface that was marshalled for a different thread?

Prefacing that I'm very new to C#: I'm trying to implement a very basic timer but I'm getting an wrong thread error in the event function of System.Timers.Timer. An image of the error is attached and the code that I'm working with is below
C#
using System.Timers;

namespace Pomodoro.Views;

public partial class MainPage : ContentPage
{
int timeLeft = 60;
private System.Timers.Timer pTimer;


public MainPage()
{
InitializeComponent();

pTimer = new(1000)
{
Enabled = true,
};

pTimer.Elapsed += timerTick;
}

private void timerTick(object sender, EventArgs e)
{
if (timeLeft > 0)
{
timeLeft--;
TimerDisplay.Text=timeLeft.ToString();
}
else
{
pTimer.Stop();
}
}


private void OnStartTimerClicked(object sender, EventArgs e)
{
pTimer.Start();
}

private void OnStopTimerClicked(object sender, EventArgs e)
{
pTimer.Stop();
}
}
C#
using System.Timers;

namespace Pomodoro.Views;

public partial class MainPage : ContentPage
{
int timeLeft = 60;
private System.Timers.Timer pTimer;


public MainPage()
{
InitializeComponent();

pTimer = new(1000)
{
Enabled = true,
};

pTimer.Elapsed += timerTick;
}

private void timerTick(object sender, EventArgs e)
{
if (timeLeft > 0)
{
timeLeft--;
TimerDisplay.Text=timeLeft.ToString();
}
else
{
pTimer.Stop();
}
}


private void OnStartTimerClicked(object sender, EventArgs e)
{
pTimer.Start();
}

private void OnStopTimerClicked(object sender, EventArgs e)
{
pTimer.Stop();
}
}
For reference TimerDisplay is referring to the label in the xaml
<Label
x:Name="TimerDisplay"
Text="60"
SemanticProperties.Description="Display of time remaining"
FontSize="50"
HorizontalOptions="Center" />
<Label
x:Name="TimerDisplay"
Text="60"
SemanticProperties.Description="Display of time remaining"
FontSize="50"
HorizontalOptions="Center" />
I feel like I might be doing something wrong from a theoretical point of view with how the eventhandler is defined.
19 Replies
.logik.
.logik.OP2y ago
Just to clarify, the error is happening as soon as I try and start the program, I see things render and then the exception. It's not in relation to clicking a button
Becquerel
Becquerel2y ago
as a high-level rule, .NET UI frameworks like winforms and WPF mandate that you only make changes to the UI on the same thread that manages the UI this is obviously a limitation, so to get around this they offer a marshaller or 'dispatcher' which handles updating the UI from other threads for you try replacing TimerDisplay.Text=timeLeft.ToString(); with Dispatcher.Invoke(() => TimerDisplay.Text=timeLeft.ToString()); you may need to add a using statement for Dispatcher
.logik.
.logik.OP2y ago
I'll give it a go, really appreciate the help. So just to clarify my understanding, the System.Timers.Timer would operate on any thread available and this may differ from the UI thread and so when the Elapsed Event is processed, it is trying to update the UI from the Timer thread throwing the error?
Becquerel
Becquerel2y ago
i forget the exactly details of how events interact with threads, but yes, i highly imagine that's what is happening this is a really common thing people encounter when they try out these frameworks incidentally, when you encounter async/await, one of the big motivations of that was to make it so you could do stuff like this without having to jump through hoops with dispatchers
.logik.
.logik.OP2y ago
I see, does that mean there is a more appropriate way of trying to accomplish what I'm doing using async/await? I'm having trouble trying to get Dispatcher to work since it doesn't seem to be in the namespace? Maybe something different with MAUI, I'll keep digging to find a way to fix it but it's a major relief having some direction on how to resolve this
Becquerel
Becquerel2y ago
oh - i'm not familiar with MAUI so it may indeed be different, my apologies assumed you were using WPF Invoker may also be a name to try
dancepanda42
dancepanda422y ago
Hi, you could try to get a reference of the UI thread through the SynchronisationContext class https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext?view=net-7.0 In the constructor store the SynchronisationContext.Current value in a Private filed. So you can call the Post mehtod in your Event handler to execute the passed method on the UI thread.
SynchronizationContext Class (System.Threading)
Provides the basic functionality for propagating a synchronization context in various synchronization models.
.logik.
.logik.OP2y ago
I'll give it a go, thanks for the suggestion
dancepanda42
dancepanda422y ago
Run code on the main UI thread - .NET MAUI
In .NET MAUI, event handlers may be called on a secondary thread. The MainThread class allows an application to run code on the main UI thread. This article describes how to use the MainThread class.
.logik.
.logik.OP2y ago
Amazing, the MainThread.BeginInvokeOnMainThread did the trick! I think the other one would have worked as well but I was struggling to get the Post method to work. All the delegate stuff was a bit too confusing and my brain is fried
.logik.
.logik.OP2y ago
I also came across this discussion earlier which I think is useful, it seems like MainThread has the potential to cause issues in certain circumstances. https://github.com/dotnet/maui/discussions/7518
GitHub
Which is better MainThread.Being/Invoke... VS Dispatcher.Dispatch.....
In MAUI, we now have multiple ways to execute code on the Main/Dispatcher/UI thread. The MainThread class was carried over from Xamarin.Essentials and offers a good, generic way to access the main ...
.logik.
.logik.OP2y ago
I'm going to try and get a bit more comfortable with Bindable objects and try to learn how the dispatcher works as well. It's kind of insane how something I expected to be simple turned out to have such a nuanced complexity.
dancepanda42
dancepanda422y ago
I think the class mainthread is the way to go. Especially when you operate on several different platforms.
.logik.
.logik.OP2y ago
Really appreciate the help from both of you. One last question if I may, should I be doing this differently? I recall notions of maybe publish subscribe update property stuff which sounds like it could keep the publish and subscribe options on separate threads though not sure if that works in a MAUI context. And otherwise is async/await a better solution or sth? I'm sorry if my questions are completely incorrect, I'm like a day or two into the whole C# environment I see, thanks
dancepanda42
dancepanda422y ago
Glad I could help.
Becquerel
Becquerel2y ago
Don't worry about async/await until you start seeing methods that return a Task type - that'll be with stuff that, say, makes HTTP calls or reads to the filesystem. It's useful to know about but it's also a big topic, so it's ok to ignore it now so you don't get overwhelmed
.logik.
.logik.OP2y ago
Fab, hopefully some of my understanding about async/await from JS will carry over. Also it turns out there is a DispatchTimer that fires on the UI thread. Turns out I went about this the hard way
Becquerel
Becquerel2y ago
oh, yeah, afaik JS was heavily inspired by C#'s async/await. We just call things Tasks instead of Promises. Best of luck in .NET
.logik.
.logik.OP2y ago
Thank you!
Want results from more Discord servers?
Add your server