Multi-thread UI, unable to BeginInvoke - c#

I have a requirement to launch a window on a separate thread from the main UI thread. It's not ideal, I know it's not normal.
The new window is launched as follows:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
var thread = new Thread(() =>
{
var w = new ScrollingBanner(300,70,0,0);
w.Show();
w.Name = "BannerThread";
w.Closed += (sender2, e2) => w.Dispatcher.InvokeShutdown();
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
This launches the window and performs the action I need it to.
The control on the Window of this (threaded) UI calls an event I'm listening for in the code behind the window, as below:
private void ContentTicker_OnScrollComplete(object sender, EventArgs e)
{
Debug.WriteLine("Scroller Ended");
try
{
if (CheckAccess())
{
sliderText.Text = "Updated Text";
}
else
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() => sliderText.Text = "Updated Text"));
}
}
catch (Exception ex)
{
//breakpoint
}
}
ContentTicker_OnScrollComplete is called from a background thread under which the control runs.
I get an exception, a different thread owns the control; but as far as I'm aware I'm calling the Dispatcher to perform the action on the correct thread. (which works if I do all this on the main UI thread)
How can I update "sliderText" on the correct thread?
Thanks

Replace this:
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() => sliderText.Text = "Updated Text"))
For this:
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() => sliderText.Text = "Updated Text"))
because Application.Current.Dispatcher will return the "Main" Dispatcher, not the "Secondary" one.

Related

How to change label.text use Task.Run()

No work await Task.Run():
private async void button2_Click(object sender, EventArgs e)
{
await Task.Run(() => {
monitor_r(label1);
});
}
protected async Task monitor_r(Label L1)
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
}
These commands
MessageBox.Show(L1.Name);
and
L1.ForeColor = Color.Blue;
works fine but
L1.Text = "test";
does not work.
Can you help, why do not change a Label Text?
Try Control.Invoke: we should run Winform UI in the main thread only
protected async Task monitor_r(Label L1)
{
Action action = () =>
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
};
if (L1.InvokeRequired)
L1.Invoke(action); // When in different thread
else
action(); // When in the main thread
}
If you're on debug mode, take a look at the output window. It should shows exception message something like this:
System.InvalidOperationException' in System.Windows.Forms.dll.
That because label1 accessed from a thread other than the thread it was created on. And it will causing invalid cross-thread operation.
You can solve this by using Control.Invoke as Dmitry Bychenko already mentioned. Here is simple extension to make thread-safe calls to Winforms Control.
public static void TryInvoke(this Control control, Action<Control> action)
{
if (control.InvokeRequired) control.Invoke(new Action(() => action(control)));
else action(control);
}
Sample usage
label1.TryInvoke(x => x.Text = "test");
label1.TryInvoke(x => x.ForeColor = Color.Blue);
Or
this.TryInvoke(x =>
{
label1.Text = "test";
label1.ForeColor = Color.Blue;
});
Secondly, since you don't await anything at monitor_r, i'd recommend to use void instead of async Task.
Even if you're await something at monitor_r you don't need
await Task.Run(() => {
monitor_r(label1);
});
..because monitor_r itself is a task. So just call await monitor_r(label1);
If you wish to have a separate thread, you can try this using BackgroundWorker. You can implement the ReportProgress if you have a loop.
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(label1.Name);
label1.ForeColor = Color.Blue;
label1.Text = "test";
}

C# WPF Cancel ProgressBar from a different Thread

I have a UserControl with a big table that is displaying values using a lot of converters. I am trying to display a ProgressBar in a new Window with Indeterminate State that is closing automatically when the UserControl Loaded event is fired.
This is the Thread creation in the backcode of my UserControl :
Thread progressBarThread = new Thread(new ThreadStart(delegate {
ProgressBarWindow progressBarWindow = new ProgressBarWindow();
progressBarWindow.IsIndeterminate = true;
progressBarWindow.LaunchProgressBarInBackground();
progressBarWindow.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => { Dispatcher.FromThread(progressBarThread).InvokeShutdown(); };
This code is "working", it is opening progressBarWindow but when I shutdown the thread using InvokeShutdown (the ugliest way to do it, i agree). The problem is that the DoWork from my backgroundWorker.
Here is the DoWork function :
private void BackgroundWorker_WaitUntilShouldStop(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Start the time-consuming operation.
while (!bw.CancellationPending)
{
Thread.Sleep(500);
}
}
I would like to call my function contained in ProgressBarWindow to stop the DoWork from runnning and close the ProgressBarWindow normaly using :
progressBar.StopProgressBarInBackground();
This method is calling backgroundWorker.CancelAsync();
This will result in backgroundWorker terminating and progressBarWindow closing automatically.
But I don't have access to progressBar that is inside the progressBarThread. I tried to pass my UserControl using :
progressBarThread.Start(this);
this being the main window.
When trying to pass a variable from the main thread, this error is thrown :
An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code Additional information: The calling thread cannot access this object because a different thread owns it.
Does someone have a nice and correct way to do it without using myThread.InvokeShutdown() ?
Edit 1 :
I found a solution to my problem using a volatile variable :
volatile bool _isLoaded;
void CreateAndStopProgressBarWhenIsLoaded()
{
Thread progressBarThread= new Thread(new ThreadStart(
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += (sender, e) => {
if (_isLoaded)
progressBar.StopProgressBarInBackground();
};
// Try to stop `progressBar` every 500 ms
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
dispatcherTimer.Start();
progressBar.ShowDialog();
// Will only be reached once progressBar.ShowDialog(); returns
dispatcherTimer.Stop();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
_isLoaded = this.IsLoaded;
progressBarThread.Join(); // Wait for progressBarThread to end
};
}
Now the question is do you have a better solution ?
Edit 2 :
Here is my final solution thanks to #AlexSeleznyov :
void CreateAndStopProgressBarWhenIsLoaded()
{
Controls.ProgressBar.ProgressBar pb = null;
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
Thread progressBarThread = new Thread(new ThreadStart(delegate
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
pb = progressBar;
manualResetEvent.Set();
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
progressBar.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
pb.Dispatcher.Invoke(delegate {
manualResetEvent.WaitOne();
pb.StopProgressBarInBackground();
});
progressBarThread.Join();
};
}
You might try this approach, to cache ProgressBar instance and then use it from another thread. Dispatcher.Invoke eradicates need for CheckAccess I've mentioned in comments.
void CreateAndStopProgressBarWhenIsLoaded()
{
var pb = new Controls.ProgressBar.ProgressBar[1];
Thread progressBarThread= new Thread(new ThreadStart(
{
Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
pb[0] = progressBar;
progressBar.IsIndeterminate = true;
progressBar.LaunchProgressBarInBackground();
progressBar.ShowDialog();
}));
progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();
this.Loaded += (sender, e) => {
pb[0].Dispatcher.Invoke(()=>pb[0].Close());
};
}
I think that you can use BackgroundWorker.RunWorkerCompleted in ProgressBarWindow - it will be invoked when you cancel a backgroundWorker.
private void backgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
//close the window
}
}

Threads and Tasks

I'm trying to open new windows form with thread and task, but this code open new windows, but do not show buttons, textfield, etc on it (form is loading)
private void newFomrm_Click(object sender, EventArgs e)
{
NewForm a = new NewForm(param); // my Form
Action showMethod = () => { a.Show(); };
Task t1 = new Task(showMethod);
Thread t = new Thread(new ThreadStart(t1.Start));
t.Start();
}
private void button1_Click(object sender, EventArgs e)
{
NewForm a = new NewForm(); // my Form
Action showMethod = () => {
Invoke(new Action(() => a.Show()));
};
Task t1 = new Task(showMethod);
Thread t = new Thread(new ThreadStart(t1.Start));
t.Start();
}
try this...you have to put the UI operation on the main thread

How to Invoke or BeginInvoke Control from another thread

from the class I am calling thread with
using (GeckoBrowserForm geckoBrowserForm = new GeckoBrowserForm(XulRunnerPath, propertyBag.ResponseUri.ToString()))
{
geckoBrowserForm.show();
}
that execute UI form OnLoad(EventArgs e)
protected override void OnLoad(EventArgs e)
{
GeckoWebBrowser m_GeckoWebBrowser = new GeckoWebBrowser();
m_GeckoWebBrowser.Invoke(new Action(() => {
m_GeckoWebBrowser.Parent = this;
m_GeckoWebBrowser.Dock = DockStyle.Fill;
m_GeckoWebBrowser.DocumentCompleted += (s, ee) =>
{
GeckoHtmlElement element = null;
var geckoDomElement = m_GeckoWebBrowser.Document.DocumentElement;
if (geckoDomElement != null && geckoDomElement is GeckoHtmlElement)
{
element = (GeckoHtmlElement)geckoDomElement;
DocumentDomHtml = element.InnerHtml;
}
if (m_Url.Equals(m_GeckoWebBrowser.Document.Url.ToString(), StringComparison.OrdinalIgnoreCase))
{
Done = true;
//m_GeckoWebBrowser.Navigate("about:blank");
//m_GeckoWebBrowser.Document.Cookie = "";
//m_GeckoWebBrowser.Stop();
}
};
m_GeckoWebBrowser.Navigate(m_Url);
}));
}
but problem is that code inside Invoke is never executed. Why? How can i execute code inside invoke?
Problem is that GeckoBrowser is a Windows Forms Control. A Control's properties and methods may be called only from the thread on which the Control was created. To do anything with a Control from another thread, you need to use the Invoke or BeginInvoke method, e.g. but how?
OnLoad is called on the UI-thread, therefore invoking is not necessary.
Besides that, you could also call these initialization steps in the constructor of the class.
Update:
To create it with a SynchronizationContext:
void ThreadFunc(object args)
{
...
var syncContext = (SynchronizationContext) args;
syncContext.Send(new SendOrPostCallback(_ => {
using (GeckoBrowserForm geckoBrowserForm = new GeckoBrowserForm(XulRunnerPath, propertyBag.ResponseUri.ToString()))
{
geckoBrowserForm.ShowDialog();
}),
null);
...
}

Displaying WPF-"NotifyIcon" on a separate thread

I am currently working on an office add-in and I need to show a notification dialog that displays progress, I'm using Philipp Sumi's wpf-notifyicon.
I need to display the notifyicon from a separate thread as I have a lot of code that already executes on the main thread, this causes the wpf-notifyicon to block and wait because the messages in the windows message queue are not being processed.
I know that I should rather execute this time consuming code on a separate thread, and display the notifyicon from the main thread and update it accordingly, but that is unfortunately not an alternative because this whole solution is single-threaded.
Example:
private FancyPopup fancyPopup;
private void button1_Click(object sender, EventArgs e)
{
notifyIcon = new TaskbarIcon();
notifyIcon.Icon = Resources.Led;
fancyPopup = new FancyPopup();
Thread showThread = new Thread(delegate()
{
notifyIcon.ShowCustomBalloon(fancyPopup, System.Windows.Controls.Primitives.PopupAnimation.Fade, null);
});
showThread.Start();
}
private void button2_Click(object sender, EventArgs e)
{
fancyPopup.TextB.Text = "Doing something...";
//Keep the main thread busy.
Thread.Sleep(5000);
fancyPopup.TextB.Text = "Done doing something...";
}
Update
I have been able to progress a little further with this updated code:
I'm creating the TaskbarIcon object on a new thread , and using Application.Run to process the application message loop on that thread...
private FancyPopup fancyPopup;
private void button1_Click(object sender, EventArgs e)
{
Thread showThread = new Thread(delegate()
{
notifyIcon = new TaskbarIcon();
notifyIcon.Icon = Resources.Led;
fancyPopup = new FancyPopup();
notifyIcon.ShowCustomBalloon(fancyPopup, System.Windows.Controls.Primitives.PopupAnimation.Fade, null);
System.Windows.Forms.Application.Run();
});
showThread.SetApartmentState(ApartmentState.STA);
showThread.Start();
}
private void button2_Click(object sender, EventArgs e)
{
fancyPopup.Dispatcher.Invoke(new Action(delegate
{
fancyPopup.TextB.Text = "Doing something...";
}));
//Keep the main thread busy.
Thread.Sleep(5000);
fancyPopup.Dispatcher.Invoke(new Action(delegate
{
fancyPopup.TextB.Text = "Done doing something...";
}));
}
I have solved my problem, I had to initialize the notifyIcon on a separate STA thread and use Application.Run in order to start pumping windows messages on that thread.
var myThread = new Thread(delegate()
{
notifyIcon = new NotifyIcon();
Application.Run();
});
myThread.SetApartmentState(ApartmentState.STA);
myThread.Start();
Then I just had to Invoke the UI of my notification dialog.

Categories