c# append text withou waiting for onclick event to finish - c#

I am doing an c# small app with a richtextbox and a button that will check if some files exists and display the result into the richtextbox.
private void RunButton_Click(object sender, EventArgs e)
{
myRichTextBox.AppendText("Starting...");
if (checkFileExists())
{
myRichTextBox.AppendText("File exists.");
}
else
{
myRichTextBox.AppendText("File NOT exists!");
}
}
The problem I am facing is that the text "Starting..." is appended into the richtextbox when the checkFileExists() has finished. Since this one can take some time I would like to display the "Starting" message at the beginning and the rest of text messages when checkFileExists has finished.
Can you help me with that?
thanks in advance!

You need to set the text on the main thread, then start the long running process (checking whether the file exists) on a separate thread. Once that's finished you can update the text with the result - but that would have to be marshalled back to the main (UI) thread.
Here's one way to do it.
private void button1_Click(object sender, EventArgs e)
{
button1.Text = "Starting...";
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => CheckIfFileExists(uiScheduler));
}
private async Task CheckIfFileExists(TaskScheduler uiScheduler)
{
await Task.Delay(2000);
var exists = true; // check if exists
await Task.Factory.StartNew(() => UpdateText(exists), CancellationToken.None, TaskCreationOptions.None, uiScheduler).ConfigureAwait(false);
}
private void UpdateText(bool exists)
{
button1.Text = $"Exists: {exists}.";
}

Related

How to update a Windows Form textbox value while waiting for a web response

I'm trying to update a status message while waiting for a web response to be returned. The call posts files to a server and sometimes it can take 30+ seconds.
I want to update the message (windows form textbox text) if the call is taking longer than expected. If the call has been waiting for 15 seconds, update the message to "This is taking awhile but should complete soon."
I've tried:
async fire and forget
timer using invoke
task.run
both tasks as async, awaiting the web calling Tasks
Background Worker using dowork and progress work
Nothing seems to work. Is it even possible to update the main thread while a task has a thread locked up?
I'm testing with simple calls:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Task.Delay(1000).Wait();
this.Invoke((MethodInvoker)delegate
{
this.box.Text += '.';
this.box.Update();
});
}
}
private void MakeCall()
{
worker.RunWorkerAsync();
using (WebClient client = new WebClient())
{
//Just runs Task.Delay(10000) then returns "Complete"
var res = client.DownloadString("https://localhost:44343/api/TestDelay");
MessageBox.Show(res);
}
worker.CancelAsync();
}
private void button_Click(object sender, EventArgs e)
{
MakeCall();
}
I think I see your problem. You are downloading on the UI thread without ever getting off of it, so the background worker can never get on it either.
Try this code out:
EDIT: Using two Tasks rather than a background worker
private void MakeCall()
{
// it'd be a good idea to disable the button here
ManualResetEventSlim waiter = new ManualResetEventSlim(false);
Task.Run(async () =>
{
while(!waiter.IsSet)
{
await Task.Delay(1000);
this.Invoke((MethodInvoker)delegate
{
this.box.Text += '.';
this.box.Update();
});
}
});
Task.Run(() =>
{
using (WebClient client = new WebClient())
{
var res = client.DownloadString("https://localhost:44343/api/TestDelay");
MessageBox.Show(res);
}
waiter.Set();
// hop back on the UI thread and re-enable your button here
});
}
private void button_Click(object sender, EventArgs e)
{
MakeCall();
}
I'm providing an additional answer here because although the answer by outbred worked in my test, it didnt work in my original program because the form needed to remain open, locking the main form, run one of the defined tasks, then close automatically. That meant the async fire and forget method wasn't an option.
What I did was overloaded the forms ShowDialog method to take an async action, trigger it, show the dialog, then close the dialog on complete.
This method works perfectly, locking the parent and allowing the background worker to update the text.
internal DialogResult ShowDialog(Action action) => ShowDialog(async () => await Task.Run(action));
internal DialogResult ShowDialog(Func<Task> action)
{
action.Invoke().ContinueWith(task => this.DialogResult = DialogResult.OK);
return this.ShowDialog();
}
Then you can call it using one of the following ways:
using (Form1 form = new Form1())
form.ShowDialog(form.MakeCall);
using (Form1 form = new Form1())
form.ShowDialog(() => { form.MakeCall("HelloWorld");} );
using (Form1 form = new Form1())
form.ShowDialog(async () => { await form.MakeCallAsync("HelloWorld");} );
It will display the form as a dialog (locking the parent), run the task to completion, then close the form.

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
}

C# - Cross Thread Error using Async and Await on TextBox

I am new to Async and Await and have created a simple project in order to understand how it works.
For this, I have a simple Windows Form application that has 2 elements:
Get Completed Items button
TextBox showing all Completed Items retrieved
When I click the button, it should display all completed Items in the TextBox.
This is the code I have written:
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
await Task.Run(() => GetCompletedItems(queueSystem));
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
private void GetCompletedItems(QueueSystem queueSystem)
{
foreach (var item in queueSystem.GetCompletedItems())
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
}
However, I am getting an error in
txtItems.Text +=
$"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
The error says
Additional information: Cross-thread operation not valid: Control
'txtItems' accessed from a thread other than the thread it was created
on.
I checked in Debug and a new thread was created for GetCompletedItems(). When I read about Async and Await, I read that it doesn't necessarily create a new thread but it seems to have created a new one for some reason.
Is my implementation and understanding of Async and Await wrong?
Is it possible to use Async and Await in a Windows Forms application?
You cannot access UI thread on a different thread. This should help
private async void btnGetCompletedItems_Click(object sender, EventArgs e)
{
QueueSystem queueSystem = QueueSystem.NewInstance(75);
Stopwatch watch = new Stopwatch();
watch.Start();
var results = await Task.Run(() => queueSystem.GetCompletedItems());
foreach (var item in results)
{
txtItems.Text += $"{txtItems.Text}{item.ItemKey}{Environment.NewLine}";
}
watch.Stop();
lblTime.Text = $"{watch.ElapsedMilliseconds.ToString()} ms";
}
You can access the thread from another thread in a following way. It does helps to avoid the cross thread exception in your application.
private void Thread()
{
this.Invoke((System.Action)(() => {
//your thread call or definition
});
}
When I read about Async and Await, I read that it doesn't necessarily create a new
thread
This is true for regular async methods. Consider this:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await DoesNothing();
}
private async Task DoesNothing()
{
// outputs the same thread id as similar line as from above;
// in particlar, for WinForms this means, that at this point
// we are still at UI thread
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}
but it seems to have created a new one for some reason
This is what Task.Run is intended for:
Queues the specified work to run on the ThreadPool
In other words, it pushes anything you pass it as a delegate to a thread pool thread. Since we are in WinForms, this means, that anonymous method () => GetCompletedItems(queueSystem) will be executed at thread pool thread, not at UI one.
Here's code sample from above with little change:
private async void button1_Click(object sender, EventArgs e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Run(DoesNothing);
}
private async Task DoesNothing()
{
// outputs DIFFERENT thread id;
// in particlar, for WinForms this means, that at this point
// we are not at UI thread, and we CANNOT access controls directly
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
}

ObjectDisposedException when form is being closed

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.

threads communication

I have a form with text box and button. On click of button I'm creating a thread and invoking it for some operation. once the thread completes the invoked task, I want to update the text box with the result.
any one please assist me how can I achieve this without thread clash.
This is far simpler using .NET 4.0's Task class:
private void button_Click(object sender, EventArgs e)
{
Task.Factory.StartNew( () =>
{
return DoSomeOperation();
}).ContinueWith(t =>
{
var result = t.Result;
this.textBox.Text = result.ToString(); // Set your text box
}, TaskScheduler.FromCurrentSynchronizationContext());
}
If you're using .NET 4.5, you can simplify this further using the new async support:
private async void button_Click(object sender, EventArgs e)
{
var result = await Task.Run( () =>
{
// This runs on a ThreadPool thread
return DoSomeOperation();
});
this.textBox.Text = result.ToString();
}
You need to use Control.Invoke to manipulate your form in it's own thread.
Simply, at the end of the thread operation:
/// ... your code here
string newText = ...
textBox.Invoke((MethodInvoker) delegate {
textBox.Text = newText;
});
The Control.Invoke usage uses the message-queue to hand work to the UI thread, so it is the UI thread that executes the textBox.Text = newText; line.
Use a BackgroundWorker, assign the task to the DoWork event, and update the text box with the RunWorkerCompleted event. Then you can start the task with RunWorkerAsync().
You can use the solutions showed here:
How to update the GUI from another thread in C#?
Next time search a bit before asking.

Categories

Resources