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

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);
}

Related

How to add an ItemsSource to a DataGrid from a async Task

I have WPF application with a Datagrid. I want to be able to run a code that retrieves data from certain location every X time and updates the DataGrid ItemsSource. The code can't interfere with UI, so it needs to run as async. I started to test the basics and yet I haven't been successful. My test compiles, but the following Exception is thrown: 'System.InvalidOperationException' in WindowsBase.dll.
I would like someone to share a simple approach to solve what I think should be a simple problem.
public void runTask()
{
List<string> list = new List<string>();
int counter = 0;
Task.Run(async () =>
{
while (true)
{
list.Add(counter.ToString());
MyDataGrid.ItemsSource = list; // This is what I want to archive
counter++; // but I get this: Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll
// Wait 2 seconds
await Task.Delay(2000);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
runTask();
}
}
}
// User starts the async task
private void Button_Click(object sender, RoutedEventArgs e)
{
runTask();
}
I presume your example is a toy example, so I'll just focus on the exception. It happens because the task is running on a different thread to the UI and you cannot update UI controls from a non-UI thread.
To interact with the UI from a non-UI thread, you use the Dispatcher:
// inside the non-UI thread
Dispatcher.Invoke(() =>
{
MyDataGrid.ItemsSource = list;
});

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);

How do I prevent webcontrol from hanging in a while loop? [duplicate]

i was doing some processor heavy task and every time i start executing that command my winform freezes than i cant even move it around until the task is completed. i used the same procedure from microsoft but nothing seem to be changed.
my working environment is visual studio 2012 with .net 4.5
private async void button2_Click(object sender, EventArgs e)
{
Task<string> task = OCRengine();
rtTextArea.Text = await task;
}
private async Task<string> OCRengine()
{
using (TesseractEngine tess = new TesseractEngine(
"tessdata", "dic", EngineMode.TesseractOnly))
{
Page p = tess.Process(Pix.LoadFromFile(files[0]));
return p.GetText();
}
}
Yes, you're still doing all the work on the UI thread. Using async isn't going to automatically offload the work onto different threads. You could do this though:
private async void button2_Click(object sender, EventArgs e)
{
string file = files[0];
Task<string> task = Task.Run(() => ProcessFile(file));
rtTextArea.Text = await task;
}
private string ProcessFile(string file)
{
using (TesseractEngine tess = new TesseractEngine("tessdata", "dic",
EngineMode.TesseractOnly))
{
Page p = tess.Process(Pix.LoadFromFile(file));
return p.GetText();
}
}
The use of Task.Run will mean that ProcessFile (the heavy piece of work) is executed on a different thread.
You can also do this by starting your task in new thread.
Just use Thread.Start or Thread. ParameterizedThreadStart
See these for your reference:
http://msdn.microsoft.com/en-us/library/system.threading.parameterizedthreadstart.aspx
Start thread with parameters
You could use BackgroundWorker component.

Application is not responding, when i tried to updated UI from another thread

Execution Flow:
From main thread I invoked the new thread(Parallel thread), which is doing a long running process.
Parallel thread is updating the main thread UI.
I made my main thread to wait until parallel thread is complete.
I need a synchronization between two thread.
I need to use the result of parallel thread in main thread so I blocked main thread until parallel process complete.
Here is my code which is having issue,
please give suggestion to resolve the issue.
private readonly AutoResetEvent _resetEvent = new AutoResetEvent(false);
private event EventHandler Workcompleted;
private void button1_Click(object sender, EventArgs e)
{
Workcompleted += Completed;
Thread thr = new Thread(UpdateUI);
thr.Start("");
_resetEvent.WaitOne();
// Logical operation dependent on parallel process final result
}
private void Completed(object sender, EventArgs args)
{
_resetEvent.Set();
}
private void UpdateUI(object txt)
{
for (int i = 0; i < 10; i++)
{
if (label1.InvokeRequired)
{
label1.Invoke(new ParameterizedThreadStart(UpdateUI), i.ToString());
}
else
{
label1.Text = (string)txt;
Thread.Sleep(100);
}
}
if (Workcompleted != null)
Workcompleted(this, new EventArgs());
}
I made my main thread to wait until parallel thread is complete.
And there you blocked yourself. Why did you start a new thread in the first place? To keep the UI responsive. And now your blocked it anyway. Do not block it. I don't know what you want to do while the thread is running, probably changing control states and resetting them when the thread is done, but what you don't want is blocking your UI thread. Stop that and find another way to achieve whatever you want to achieve.
It seems you are looking for a way to report progress in the UI during the course of the parallel operation and wait for the final result (synchronize) to do something with it.
This could easily be accomplished using Async/Await, without having to run manual threads, synchronization constructs or thread marshaling (for UI invocation) and most importantly without blocking the UI thread.
Here is an example of how to run a parallel operation, report progress back to the UI, update UI continuously and finally do something with the result when it is available.
private async void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(ShowProgressInUi);
var result = await Task.Run(() => DoParallelWorkAsync(progress));
// Do something with final result
label1.Text = result;
}
private void ShowProgressInUi(int progress)
{
label1.Text = string.Format("Progress: {0} % done...", progress);
}
private static async Task<string> DoParallelWorkAsync(IProgress<int> progress)
{
// This work is done in a separate thread.
// In this case a background thread (from the thread pool),
// but could be run on a foreground thread if the work is lengthy.
for (var i = 1; i <= 10; i++)
{
// Simulate workload
await Task.Delay(100);
progress.Report(i * 10);
}
return "All done";
}
public delegate void Action();
private void UpdateUI(object txt)
{
this.BeginInvoke((Action)(() =>
{
label2.Text = (string)txt;
}));
}
By using this code, we don't need to wait for another thread...

C# Asynchronous Task for a Stock Ticker

I've been trying to learn more about asynchronous tasks and threading but not making a ton of headway.
I'm trying to load an "Engine" type of thread that will run in the background upon launch and be able to access the UI Thread to update variables, without hanging the UI Thread.
In the below code, Engine is called, and a Ticker object is created which holds the current value of (Litecoin/USD) called Last, also holds several other values that would be useful. This code successfully assigns the current value to label1.text. I don't necessarily need code but what approach would I take to create a ticker object in the background every second and update the UI thread with each new Ticker objects values.
Is this a good case for a background worker?
private void Form1_Load(object sender, EventArgs e)
{
Engine();
}
private void Engine()
{
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD:" + ltcusd.Last;
}
EDIT:
If I do the following, label1 throws an InvalidOperationException due to a Cross-thread operation attempt (label1 in the UI thread).
private void Form1_Load(object sender, EventArgs e)
{
var t = Task.Factory.StartNew(() => Engine());
t.Start();
}
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
}
Using async/await, the simplest way of getting an "asynchronous" sort of API is to invoke a new task. It's not great, but it'll make things simpler. I would probably create a new class which basically wrapped all the BtceApi methods in tasks:
public class BtceApiAsync
{
public Task<Ticker> GetTickerAsync(BtcePair pair)
{
return Task.Run(() => BtceApi.GetTicker(pair));
}
// etc
}
Then you can use a timer which fires once per second, which will start off a new task and update the UI appropriately:
// Keep a field of type System.Windows.Forms.Timer
timer = new Timer();
timer.Interval = 1000;
timer.Tick += DisplayTicker;
timer.Start();
...
private async void DisplayTicker(object sender, EventArgs e)
{
Ticker ticker = await BtceApiAsync.GetTickerAsync(BtcePair.LtcUsd);
label1.Text = "LTC/USD: " + ltcusd.Last;
}
Note that this doesn't mean the screen will be updated once per second... there will be a new task started once per second, and as soon as each task completes, the UI will be updated.
The use of await here - from an async method started on the UI thread - means you don't need to worry about using the UI; the whole async method will execute on the UI thread, even though the fetch itself happens in a different thread.
You can try ContinueWith to update the Label at the end of the task. If you want to update it event before the task ends then raise an event which is registered by on the UI thread. The event can then update the label.
I suppose this is Windows Forms. You could do it "old school style" and set the label text on the UI thread, and you can do that by passing delegate to the BeginInvoke or Invoke method.
private void Engine()
{
while (true)
{
Thread.Sleep(1000);
Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
UpdateText("LTC/USD: " + ltcusd.Last);
}
}
private void UpdateText(string text)
{
//Inspect if the method is executing on background thread
if (InvokeRequired)
{
//we are on background thread, use BeginInvoke to pass delegate to the UI thread
BeginInvoke(new Action(()=>UpdateText(text)));
}
else
{
//we are on UI thread, it's ok to change UI
label1.Text = text;
}
}

Categories

Resources