This question already has answers here:
How to put delay before doing an operation in WPF
(2 answers)
Is it possible to change views, once during the start of an event handler and once during end?
(4 answers)
Closed 3 years ago.
I'm trying to emulate a process where buttons are sequentially getting set to one colour then back to their previous colour every few seconds.
Problem is, I am not entirely sure how to iterate through a collection without blocking the UI thread... i don't like using thread.sleep but this is what i found on SO.
Anyone aid me? Posting code I have below for Start button click
private void StartTest_Click(object sender, RoutedEventArgs e)
{
//tabControl.SelectedIndex = 3;
// Emulate buttons being processed
Dispatcher.Invoke(() =>
{
new Task(() =>
{
selectedButtons.All(bSelected=>
{
bSelected.Background = resourceDictionary["ProcessingButtonColour"] as Brush;
Thread.Sleep(3000);
bSelected.Background = resourceDictionary["HoverOverColourInactive"] as Brush;
return true;
});
}).Start();
});
}
You shouldn't need Dispatcher.Invoke here if your button is clicked from the UI then your on the UI thread. Just loop through the buttons and change their color. No reason to Select just to return a dummy true value. Also, no reason hardly ever to use a Task constructor, especially not to run work back on the UI thread. Something like this should work:
private async void StartTest_Click(object sender, RoutedEventArgs e)
{
foreach (var button in selectedButtons)
{
button.Background = resourceDictionary["ProcessingButtonColour"] as Brush;
await Task.Delay(3000);
button.Background = resourceDictionary["HoverOverColourInactive"] as Brush;
}
}
Related
This question already has answers here:
An async/await example that causes a deadlock
(5 answers)
Closed 4 years ago.
I know there are quite a lot of Post about Task deadlocks, but I simply can't find the right sollution.
So I basically have this setup:
public event EventHandler StateChangedEvent;
public bool Busy
{
...
set
{
...
this.StateChangedEvent?.Invoke(this, EventArgs.Empty)
}
}
public void Main()
{
...
this.StateChangedEvent += this.OnStateChangedEvent;
}
public void OnStateChangedEvent(object sender, EventArgs e)
{
this.TextBox.Invoke(() => this.TextBox.Text = "Change");
this.Invoke(() => this.Cursor = Cursors.WaitCursor);
}
public void ButtonAction_Click(object sender, EventArgs e) //actually part of an API with an virtal - override method on between. Can't change here to async
{
...
Task.Run(async () => await this.AsyncDoStuff()).Wait();
... // Synchron stuff needs to be done afterwards
}
public async Task AsyncDoStuff()
{
this.Busy = true; //Causes Deadlock
await Stuff1();
await Stuff2();
}
So in reality those calls are split among different classes, but the basic structure remains. Yes I know I should go async all the way up, but let's say the first ButtonAction_Click is part op an API/Framework and can't be changed to async.
I know the reason is because I block the UI Thread and then access it again...So what is the best solution for this?
Thanks in advance!
So from your other comments it sounds like the code in ButtonAction_Click is outside your control and you can't change it. Unfortunately, that's where the problem is - this event handler is totally blocking the UI thread until the work has finished. There's no way to unblock the thread.
Your only recourse is to avoid any blocking interactions with the UI thread.
The following code from your example is definitely going to cause deadlock, because Invoke() will block until the UI (which is already blocked) responds:
this.TextBox.Invoke(() => this.TextBox.Text = "Change");
this.Invoke(() => this.Cursor = Cursors.WaitCursor);
You could try using BeginInvoke() instead of Invoke(), but the unfortunate consequence is that these UI changes won't actually execute until the UI thread is unblocked, and by then your background work is already finished. It might fix your deadlock, though.
This question already has answers here:
Not updating GUI in time
(2 answers)
WinForm Application UI Hangs during Long-Running Operation
(3 answers)
Accessing UI controls in Task.Run with async/await on WinForms
(6 answers)
Closed 5 years ago.
I have a function which makes process. At the beginning of the function I want to change the text of a label which indicates the state, and once the process ends, change it again.
The fact is that only is shown the final change.
I know I can make a thread for the process but not needed in this case and I merely want to know if there's some tip or trick to accomplish it whitout the use of a thread.
In this case When you change something in UI it does not change until the whole process is completed so as you said you can only see the final state of your label. The trick is simple. You need to use a secondary thread while you are going to update the label for the fist time.
Look at the example below :
class Program
{
private delegate void del();
static void Main(string[] args)
{
del d = updateConsole;
var res = d.BeginInvoke(null, null);
while (!res.IsCompleted)
{
// do the job here
}
d.EndInvoke(res);
Thread.Sleep(1000);
Console.Clear();
Console.WriteLine("Done!");
Console.Read();
}
private static void updateConsole()
{
for (var i = 0; i <= 10; i++)
{
Thread.Sleep(500);
Console.Clear();
Console.WriteLine(i);
}
}
}
This simple technique helps you to separate the first update of the label from the whole process.
I am trying to implement an indeterminate progress bar into my program. I'm new to threading, but as far as I know one of the best options here is to add an async method, and await the "heavy" function to perform its results. So I wrote this:
public void Window_Loaded(object sender, RoutedEventArgs e)
{
firstLoad();
}
private async void firstLoad()
{
LW.Title = "Loading...";
LW.Show();
filterTextBox.Text = defaultSearch;
await Task.Run(() => InitializeFilter());
}
private void InitializeFilter()
{
//Asynchronous???
Dispatcher.BeginInvoke(new Action(() => {
//... some lines of code that takes some time to run.
dataGrid.ItemContainerGenerator.StatusChanged += new EventHandler(closeLoadingWindow);
}));
private void closeLoadingWindow(object sender, EventArgs e)
{
if (LW != null)
{
LW.closable = true;
LW.Close();
}
}
firstLoad runs when the window is loaded, showing an indeterminate LW loadingWindow, and running the InitializeFilter() method (the heavy one). Finally, when the grid is populated and loaded, an event fires, allowing the LW window to be closed and closing it (if I didn't make it unclosable, a funny user could just close it clicking or using F4, which is not nice).
The system is working properly and everything works as expected regarding time frames, but the loading bar is frozen, not showing progress. The same LW bar works in the MainWindow with a similar set up What am I missing? Thanks in advance!
as far as I know one of the best options here is to add an async method, and await the "heavy" function to perform its results
The best option is to use Task.Run to move the heavy processing to the thread pool, and use await to retrieve its results.
The code as it currently stands uses Task.Run to move to the thread pool and then immediately turns around and uses Dispatcher to move back to the UI thread before doing the heavy processing. Thus, it's blocking the UI thread.
what this particular DataGrid displays is a CollectionView, which is not thread-safe.
Right, you can't update data-bound objects from a thread pool thread.
The best solution is to separate the heavy processing from the UI updates, something like this:
public async void Window_Loaded(object sender, RoutedEventArgs e)
{
await firstLoadAsync();
}
private List<FilterType> InitializeFilter()
{
//... some lines of code that takes some time to run.
}
private async Task firstLoadAsync()
{
LW.Title = "Loading...";
LW.Show();
filterTextBox.Text = defaultSearch;
var filterData = await Task.Run(() => InitializeFilter()); // Get the plain data on a background thread
myCollectionView = new CollectionView(filterData); // Update the UI
if (LW != null)
{
LW.closable = true;
LW.Close();
}
}
do not use your dispatcher. Microsoft had the foresight to use it's magic (SynchronizationContext) to be able to update the UI thread in a method that is being executed in an async context. This is demonstrated in their async/await example found here
while under previous/other circumstances, you would have to either marshal back to the main (UI) thread to update the UI thread, or wait until completed and retrieve the results from objects who share state. Since you are using async/await then you should be fine to not use the dispatcher, and update the UI directly.
I have seen a lot of questions about how to edit controls on c# form from a different thread but none make much sense to me. I understand that you can not change any UI from another thread than it's main. To make this work you have to use invoke and from there safely edit the control?
I have a button that starts writing in a file and the moment you press the button the button itself gets disabled so you can not start multiple threads that do exactly the same. When the writing is done I want the button to be available again but I can not get it working on this other thread.
I have this as the Generate_Click event from the form.
private void Generate_Click(object sender, EventArgs e)
{
Generate.Enabled = false;
int x = 512;
int y = 512;
MBrot mbrot = new MBrot(x, y);
PB_Update lb = new PB_Update(0, y, Generator_PB, Generate, mbrot, this);
lb.Start();
}
And this is in PB_Update.cs the ThreadWork() function, when the while loop is done the writing to the file is done and so is the thread so its ended and given a messagebox with "finished" now as last the button needs to be enabled again.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.Enabled = true;
}
For WinForms you can execute directly on the thread which the control was created on through the Control.BeginInvoke method, you can use Control.Invoke as well but, Control.BeginInvoke is preferred for UI operations.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.BeginInvoke((Action)delegate()
{
Generate_.Enabled = true;
});
}
Somehow, get a reference to the form that hosts the generate_ button (let's call it myform). Then, at the bottom of your ThreadWork:
myform.Invoke(new Action(() => {
myform.SetGenerateEnabled();
}));
And then inside your form create that method that enables the button appropriately. (I used a method rather than just updating the button directly so that you don't publicly expose the button.)
This executes the commands inside the { ... } on myform's thread, which is a UI thread, because it is UI. At least, that's what I understand. This is how I do all of my UI updating from other threads.
Here's a simple example of a way to kick off an async task that disables a button for 5 seconds and then enables it again. Meanwhile, the rest of the UI is functional.
Note that this async method exists in the same class as your Generate_Click event, and runs on the UI thread. This means that it can enable and disable the button. But the long running task executes on a separate thread, so it doesn't lock the UI.
Hopefully this sample provides you a base to modify for your own code:
private void Generate_Click(object sender, EventArgs e)
{
DisableButton(sender as Button, 5);
}
private async void DisableButton(Button sender, int secondsToDisable)
{
sender.Enabled = false;
// In your code, you would kick off your long-running process here as a task
await Task.Run(()=>Thread.Sleep(TimeSpan.FromSeconds(secondsToDisable)));
sender.Enabled = true;
}
This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
Closed 9 years ago.
Hi I am first time working on Threads not sure If I am doing correct
I am getting Error saying :
The calling thread cannot access this object because a different thread owns it" exception
private void ImportProductStatsButtonClick(object sender, EventArgs e)
{
// Get the currently selected manufacturer from the combo box
var selected = comboBoxCorporation.SelectedItem;
buttonProductStatsAndRetailerStats.Enabled = false;
buttonSummariseRetailerStats.Enabled = false;
buttonSummariseProductStats.Enabled = false;
// Do we have one?
if (selected != null)
{
// Extract the combo record
var corporation = (ComboBoxCorporrationItem)selected;
// Do we have one?
if (corporation.Corporation != null)
{
// yes
// Make this on a seperate thread so that the UI continues to work
var thread = new Thread(MigrateProductStats);
thread.Start(corporation.Corporation.Id); // This enables me to pick the manufacturer that we are summarizing for
}
}
}
private void MigrateProductStats(object corporationIdObj)
{
// after thread completion I need to Enable my buttons.
buttonProductStatsAndRetailerStats.Enabled = true;
buttonSummariseProductStats.Enabled = true;
}
Try with:
private void MigrateProductStats(object corporationIdObj)
{
Invoke(new Action(() =>
{
// after thread completion I need to Enable my buttons.
buttonProductStatsAndRetailerStats.Enabled = true;
buttonSummariseProductStats.Enabled = true;
});
}
Even better than Control.Invoke would be to use BackgroundWorker to handle threading for you. It generates progress and completion events back on the UI thread to make UI updates easy.
If you're using C# 5, you can also use async to start the background processing and await to cause the UI updates to occur when processing completes.