ObjectDisposedException when form is being closed - c#

I have a timer on WinForm which I start when the form loads:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (true)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
The problem is when I start the app in Debug mode in VS and the close it. I get an ObjectDisposedException which states that my form is already disposed.
I tried to fix it the following way:
private bool _runningTimer = true;
public MainForm()
{
InitializeComponent();
// ...
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
_runningTimer = false;
}
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => lblTime.Text = DateTime.Now.ToLongTimeString();
Task task = new Task(() => {
while (_runningTimer)
{
Invoke(action);
Task.Delay(1000);
}
});
task.Start();
}
But the problem still ocurrs. What Am I doing wrong here?
UPDATE: I know that there is a standart timer for WinForms that works great in multithreaded invironment. I just wanted to know how it is possible to make it work to better understand how to deal with race conditions. This kind of timer is just an example, it could be another process that needs to update GUI.
UPDATE 2: Following the Hans Passant and Inigmativity answers I came to that code:
private void MainForm_Load(object sender, EventArgs e)
{
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
Task task = new Task(async () => {
while (!IsDisposed)
{
Invoke(action);
await Task.Delay(1000);
}
});
task.Start();
}
But anyway if I make time interval, for example 100ms, the ObjectDisposedException still throws.
This is not real life example, I just experimenting with it...

In your first example the Task has no idea your app is exiting and is at risk of invoking the action after the label is destroyed, hence the ObjectDisposedException.
Though you attempt to alert the task in the second example, it isn't really that thread-safe and you could still invoke the action after the control is disposed.
Timers
A better solution is to just use a WinForms Timer. If you place the timer on the form via the designer, it automatically registers it as a component dependency making lifetime management much easier.
With WinForm timers you don't need to worry about threads or Tasks and more importantly you won't need to worry about Invoke if you need to update the UI (as opposed to child threads or non-UI context tasks)
Tell me more
How to: Run Procedures at Set Intervals with the Windows Forms Timer Component

Ok, I tried to use task cancellation the following way:
public MainForm()
{
InitializeComponent();
cts = new CancellationTokenSource();
Load += MainForm_Load;
FormClosing += MainForm_FormClosing;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cts.Cancel();
}
private void MainForm_Load(object sender, EventArgs e)
{
CancellationToken ct = cts.Token;
Action action = () => { lblTime.Text = DateTime.Now.ToLongTimeString(); };
var task = Task.Factory.StartNew(async () => {
ct.ThrowIfCancellationRequested();
while (true)
{
Invoke(action);
await Task.Delay(100);
}
}, ct);
}
Don't know whether it's right but it seems works even if the time interval set to 10 ms.

Related

C# WPF.Net Core cannot Close window from other thread

Currently I'm working on the WPF on .net core.
My application have to start Cef core to run the UI (instead of using WPF form).
Before of that I want to display a simple WPF window that say "Loading..."
So in the application start up, I have to start a thread like this
Thread thread = new Thread(() =>
{
try
{
DisplayLoader = true;
var f = new Loading();
f.Loaded += (a, b) =>
{
Task.Run(() =>
{
while (DisplayLoader)
Thread.Sleep(250);
f.Dispatcher.BeginInvoke(() =>
{
f.Close();
});
f.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(delegate ()
{
f.Close();
}));
});
};
f.Show();
Dispatcher.Run();
}
catch
{
Loader.Close();
}
});
The point is, when the DisplayLoader become false, I saw the Dispatcher invoke the Close function too, however, nothing happened. I already follow a lot of answer on stackOverflow, but none of them works.
Below the thread start is this function, It will invoke Cef and display a Cef Window
thread.Start();
var assembly = Assembly.GetExecutingAssembly();
CefApp
.Run(assembly)
When the CefApp Loaded, the DisplayLoader will be set to false.
protected override void OnLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode)
{
base.OnLoadEnd(browser, frame, httpStatusCode);
if (frame.IsValid)
{
if (App.DisplayLoader)
{
App.DisplayLoader = false;
}
}
}
EDIT:
The problem actually come from the CefAppBuilder, it embedded C++ code from CefGlue, then may cause some issue with C# function. Just do not modify any C# variable in C invoke function then it's fine.
The thread that executes a Window must be a UI thread which must be a STA thread.
You have to mark the thread as STA using Thread.SetApartmentState:
App.xaml
private void Run(object sender, StartupEventArgs e)
{
Thread uiThread = new Thread(DoWork);
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
}
But my recommended approach is to execute the initialization asynchronously. This avoids the overhead of creating additional UI threads and is also more compact in terms of lines of code and readability:
App.xaml
private TaskCompletionSource TaskCompletionSource { get; set; }
private async void Run(object sender, StartupEventArgs e)
{
Window splashScreen = new SplashScreenWindow();
splashScreen.Show();
await InitializeCefAppAsync();
splashScreen.Close();
}
private async Task InitializeCefAppAsync()
{
this.TaskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.LongRunning);
var assembly = Assembly.GetExecutingAssembly();
CefApp.Loaded += OnCefAppLoaded;
// Consider to implement an awaitable CefApp.InitializeAsync method
// instead of calling Run directly. This way you can remove the TaskCompletionSource pattern
CefApp.Run(assembly);
return this.TaskCompletionSource.Task;
}
private void OnLoaded(object sender, EventArgs e)
{
this.TaskCompletionSource.SetResult(true);
}

C# WPF Update Label before and after processing - immediately

I have already tried several online examples (Thread, Dispatcher, await/async) but none is working for me in my C#/WPF project.
I have the following button click method:
private void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
executeInstall(); //do some work
this.lblResponse.Content = "DONE";
}
The label gets updated afterwards to DONE, but when I click again on the button the label isnt getting emptied before the processing of executeInstall.
As I mentioned I already tried several different examples from other questions (Dispatcher.BeginInvoke, Thread, Task, await/async) but none of them has worked - the label change before is never done before the processing of executeInstall.
I am working in .NET framework 4.7.2.
Is there maybe a setting that debug mode only executes the program with one thread and thats maybe why none of the solutions works for me?
Use async for that.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
this.lblResponse.Content = "";
await Task.Run(()=> executeInstall());
this.lblResponse.Content = "DONE";
}
UPDATE: If you need to access the UI inside your executeIntall method you will need to invoke the Dispatcher. In this case you would need to delay the Task to give the label time to update before the install starts. Note that this will cause the UI to freeze during the entire install.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Delay(100).ContinueWith(_=>
{
App.Current.Dispatcher.Invoke(() =>
{
executeInstall();
lblResponse.Content = "DONE";
});
});
}
A better approach would be to only call the dispatcher when it's actually needed. This would keep the UI responsive during the entire process.
private async void BtnInstall_Click(object sender, RoutedEventArgs e)
{
lblResponse.Content = "starting...";
await Task.Run(()=> executeInstall());
lblResponse.Content = "DONE";
}
private void executeInstall()
{
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Downloading Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Unzipping Files...");
Thread.Sleep(1000); //do time consuming operation
App.Current.Dispatcher.Invoke(() => lblResponse.Content = "Updating Files...");
Thread.Sleep(1000); //do time consuming operation
}

winforms: updating progress with a parallel.foreach

I haven't seen any posts pertaining to my issue, so I apologize if I post a question already asked.
I have a windows form program, c#, that checks stocks and does analysis. The main form launches another form, via a new thread and ShowDialog. While it's loading, it's running a parallel.foreach. In that parallel.foreach, I'd like to show progress on the main form.
I've run into cross-threading issues, and added invoke, although it doesn't appear to be thread-safe as it seems to be deadlocking toward the end of the parallel.foreach. I've tried delegates, events, no luck. Help me Obi-Wans, you're my only hope!
Stripped down version:
Main form
private void button1_Click(object sender, EventArgs e)
{
YearLows yearLows = new YearLows();
Thread yearLowsThread = new Thread(() => StartYearLows(yearLows));
yearLowsThread.Start();
btnGetYearLows.Enabled = false;
}
private void StartYearLows(YearLows yearLows)
{
yearLows.ShowDialog();
}
public void UpdateProgress(string text)
{
lblProgress.Text = text;
}
2nd form dialog
public partial class YearLows : Form
{
private void YearLows_Load(object sender, EventArgs e)
{
// work
Parallel.ForEach(responseStocks, new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, x =>
{
// more work
Interlocked.Increment(stocksProcessed);
UpdateProgress($"{stocksProcessed} / {stocksTotal} Researched");
});
}
private void UpdateProgress(string text)
{
Invoke(new Action(() => frmMain.UpdateProgress(text)));
}
}
Update 1:
If I move the progress update label to the child form, it appears I am getting all the progress updates. I had to move from the Load event to the Shown event so that the form renders, so users can see the progress updates. I had to follow SLaks advice though and run Task.Run(() => Parallel.ForEach. This will work for me. Would still like to figure out why it still locks up toward the end if I wanted the progress updates on the main form. (I've always read async void was bad, but I guess no way around this in these defined method signatures in winforms)
public partial class YearLows : Form
{
private async void YearLows_Shown(object sender, EventArgs e)
{
await AnalyzeStocks();
}
private async Task AnalyzeStocks(object sender, EventArgs e)
{
// work
await Task.Run(() => Parallel.ForEach(responseStocks, new ParallelOptions { MaxDegreeOfParallelism = MaxThreads }, x =>
{
// more work
Interlocked.Increment(stocksProcessed);
UpdateProgress($"{stocksProcessed} / {stocksTotal} Researched");
}));
}
private void UpdateProgress(string text)
{
Invoke(new Action(() => lblProgress.UpdateProgress(text)));
}
}
Parallel.ForEach is a blocking call; it runs delegates on the calling thread too. Therefore, the UI cannot update until it finishes.
Instead, you should use await with Task.WhenAll (if you're doing async work) or Task.Run(() => Parallel.ForEach(...)) (if it's CPU-bound) so that you leave the UI thread idle and able to update.
you can use Async Await function for this puprose... this link can be more useful to you...
PictureBox animation freezing while Task running
As per SLaks answer, an example of using Task.Run, with UI update
var tasks = new List<Task>();
foreach (var result in results)
{
tasks.Add(Task.Run(async () => {
// DO WORK
Dispatcher.Invoke(() =>
{
// UPDATE THE UI, I.E. ProgressBar.Value++;
});
}));
}
await Task.WhenAll(tasks);

UI still locked up even with multi threading

I'm working on a project and I've hit a lot of road blocks with the UI.
In this case create a new thread to update the UI so that the UI main thread is left open allowing you to still use the UI.
I'm not sure what the problem with this is. I think that maybe I'm using dispatcher to create thread that instead of creating a thread uses the main thread?? Its also worth noting that my MainWindow is singleton instance.
private void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
Thread t1 = new Thread(new ThreadStart(
delegate
{
this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(start));
}));
t1.Start();
//await Task.Run(() => start());
}
}
You don't need to create a new thread. When you're using async and await, a new thread will be automatically assigned from the thread pool. Just make your button_Click event handler async as mentioned in code below and call the long running task using await keyword.
private async void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
await Task.Run(() => start());
}
}
What will happen here is when you click on the button the event will be handled. If the condition is satisfied, the long running task is initiated in this line await Task.Run(() => start());. Then it will return to the UI thread without blocking it ie the UI will still be responsive while another thread is executing the long running process in background.
Please read Asynchronous Programming with async and await (C#) on MSDN.
EDIT :
Since you want to control UI elements in your start() method, use the below approach :
private async void button_Click(object sender, RoutedEventArgs e)
{
if (go)
{
city = textBox.GetLineText(0);
if (city == "exit")
{
Environment.Exit(0);
}
await start();
}
}
private async Task start()
{
await Task.Run(async () =>
{
// long running task here
});
// UI Control code here.
}

C# in Async Task change Label Text

The following Code does not change the Text and stops executing the Task
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
Task.Run(() => MyAsyncMethod());
}
public async Task MyAsyncMethod()
{
label1.Text = "";
//everything from here on will not be executed
}
would be really handy if you could use async together with the UI
for accessing a GUI control through a second thread you need to invoke.
following example shows how to set a label's text properly
private void setLabel1TextSafe(string txt)
{
if(label1.InvokeRequired)
label1.Invoke(new Action(() => label1.Text = txt));
else
label1.Text = txt;
}
I hope this solves your problem
would be really handy if you could use async together with the UI
The design of async was carefully done so you can use it naturally with the UI.
in my code i run a function that does a lot of IO and stuff that takes a long time
If you have asynchronous I/O methods (which you should), then you can just do this:
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
await MyMethodAsync();
}
public async Task MyMethodAsync()
{
label1.Text = "";
await ...; // "lot of IO and stuff"
label1.Text = "Done";
}
That's the most natural approach.
However, if you need to run code on a background thread (e.g., it's actually CPU-bound, or if you just don't want to make your I/O operations asynchronous like they should be), then you can use IProgress<T>:
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
var progress = new Progress<string>(update => { label1.Text = update; });
await Task.Run(() => MyMethod(progress));
}
public void MyMethod(IProgress<string> progress)
{
if (progress != null)
progress.Report("");
...; // "lot of IO and stuff"
if (progress != null)
progress.Report("Done");
}
Under no circumstances should modern code use Control.Invoke or (even worse) Control.InvokeRequired.
Task.Run is used to envelope an Action (that is not async) into a Task. Any Task that you want to execute should be awaited. Thus, that Task.Run of yours is rigorously doing nothing.
Mark that button1_Click event handler of yours as async. Then remove that Task.Run and instead do await MyAsyncMethod().
Try this. You don't need to fire a new thread to invoke the async method. compiler will do it for you.
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "Test";
MyAsyncMethod();
}
public async Task MyAsyncMethod()
{
return await Task.Run(() =>{
label1.Text = "";
//everything from here on will not be executed
}
}
I think both the questions and some of the answers are not clear. Depending on where in the task thread you need to update the label you have to use invoke. Otherwise you can leverage await and leverage the standard semantics.
private async void button1_Click(object sender, EventArgs e)
{
label1.Text = "Starting to run a long task... carry on...";
await snooze(3);
label1.Text = "Done with long task";
}
public Task<int> snooze(int seconds)
{
label1.Text = "zzzz...";
return Task.Run(
() => {
label1.Invoke(new Action(() => label1.Text = "For some reason I need to alert you here.. bad dream perhaps...direct access to label1 will fail"));
Thread.Sleep(seconds * 1000);
return seconds;
});
}

Categories

Resources