Run Async/Await Task in BackgroundWorker's RunWorkerCompleted - c#

I have a BackgroundWorker that runs a job generating a large amount of text.
When it's complete, I need it to execute an Async/Await Task Method, which writes and colorizes the text in a RichTextBox.
The Async/Await Task is to prevent the MainWindow UI thread from freezing while work is being calculated, such as searching and colorizing, for the RichTextBox.
Error
Exception: "The calling thread cannot access this object because a differnt thread owns it."
I get this error unless I put the Async/Await code inside a Dispatcher.Invoke.
But using a Dispatcher.Invoke seems to negate the Async/Await and cause the MainWindow UI thread to freeze.
C#
public void Generate()
{
// Background Worker
//
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(delegate (object o, DoWorkEventArgs args)
{
BackgroundWorker b = o as BackgroundWorker;
// Generate some text
// ...
});
// When Background Worker Completes Job
//
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(delegate (object o, RunWorkerCompletedEventArgs args)
{
// Write and Colorize text in RichTextBox
Task<int> task = Display();
bw.CancelAsync();
bw.Dispose();
});
bw.RunWorkerAsync();
}
// Method that Writes and Colorizes text in RichTextBox in MainWindow UI
//
public async Task<int> Display()
{
int count = 0;
await Task.Run(() =>
{
// Problem here, it will only work inside a Disptacher
//Dispatcher.Invoke(new Action(delegate {
// Write text
Paragraph paragraph = new Paragraph();
richTextBox1.Document = new FlowDocument(paragraph);
richTextBox1.BeginChange();
paragraph.Inlines.Add(new Run("Test"));
richTextBox1.EndChange();
// Colorize Text here...
// Is a loop that takes some time.
// MainWindow UI freezes until it's complete.
//}));
});
return count;
}

I agree with others that the code would be cleaner once you replace BackgroundWorker with Task.Run. Among other things, it's much easier to compose with await (by "compose", I mean "do this thing; then do this other thing"). Note that you should use await instead of ContinueWith.
So your original code would end up looking something like this once the BGW is converted to Task.Run:
public string GenerateSomeText(CancellationToken token)
{
// Generate some text
}
public async Task GenerateAsync()
{
var cts = new CancellationTokenSource();
var result = await Task.Run(() => GenerateSomeText(cts.Token));
await DisplayAsync(result);
}
So, on to the issue that prompted this question in the first place: how to do lots of UI work without blocking the UI? Well, there isn't a great solution, because the work is UI work. So it can't be put on a background thread. If you have tons of UI work to do, the only real options are:
Virtualize your data. This way you only need to process the amount of UI you are displaying. This is the best solution to this problem.
If you don't want to put in the work to virtualize your data, then you can put in a hack where your code periodically pauses the UI work so that the UI remains responsive.
I do not believe the WPF RichTextBox supports virtualization, so you may need to go third-party for that. If you wanted to do the pause hack instead, you could do something like this:
public async Task<int> DisplayAsync(string text)
{
int count = 0;
// Write text
Paragraph paragraph = new Paragraph();
richTextBox1.Document = new FlowDocument(paragraph);
richTextBox1.BeginChange();
paragraph.Inlines.Add(new Run(text));
richTextBox1.EndChange();
// Colorize Text here...
// Is a loop that takes some time.
for (loop)
{
... // Colorize piece of text.
await Task.Delay(TimeSpan.FromMilliseconds(20));
}
return count;
}

Related

Can't understand how to use Progress(T) to update UI during async process

I have been googling this for quite a few hours, and read quite a few SO questions where this is discussed but I am sorry to say I just don't get how to use it.
Basically what I am trying to do is to have a label in a WPF/Win Forms app display the following while an async task is running:
Processing .
and at each 1 second interval to add another dot until I get to three and then start over at 1 until the task is done.
As a first step I am only trying to add a dot after each second and have tried it with an IProgress action but the only thing that I have been able to accomplish is either nothing or the label gets populated with dots in one shot and the other task seems to run after that is done.
I next tried doing the following:
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Text = "Waiting for the response ...";
startButton.IsEnabled = false;
resultsTextBox.Clear();
var task = SumPageSizesAsync();
var progress = Task.Run(() =>
{
var aTimer = new System.Timers.Timer(1000);
aTimer.Elapsed += OnTimedEvent;
aTimer.AutoReset = true;
aTimer.Enabled = true;
void OnTimedEvent(object source, ElapsedEventArgs et)
{
if (!lblProgress.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(() =>
{
lblProgress.Content += ".";
});
}
}
});
await task;
await progress;
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
startButton.IsEnabled = true;
}
But again the label just gets populated with dots at once while the other task keeps running.
I took this example from the Microsoft Docs
UPDATE:
I have now tried removing the loop while(!task.IsComplete) which basically makes the label start to be updated after the first task has finished. Then I tried to the following:
var task = SumPageSizesAsync();
var progress = GetProgress();
await Task.WhenAll(SumPageSizesAsync(), GetProgress());
But got the same result, the label begins to update after the first task has concluded.
Thank you for your help.
"Progress(T)" is the wrong pattern for this.
Here is the code for a WPF application that does this with 100% async / await code, no additional threads are created.
It starts two async tasks. The first simulates the long running async process. The second one starts another async Task that takes the first task as a parameter. It loops until the first task is completed, while updating a label with a "..." pattern. It awaits a Task.Delay to control the animation speed.
Both those tasks are placed in to a list, and the we await the completion of both of them.
This could all be wrapped up in in to a ShowProgressUntilTaskCompletes method (or extension method) that takes the worker Task as a parameter, which gives you an easily reusable method of showing a progress indicator for any Task.
MainWindow.xaml:
<Window
x:Class="LongProcessDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<StackPanel Margin="100" Orientation="Vertical">
<Button Click="StartProcess_OnClick" Content="Start" />
<TextBlock
Name="LoadingText"
Padding="20"
Text="Not Running"
TextAlignment="Center" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
namespace LongProcessDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void StartProcess_OnClick(object sender, RoutedEventArgs e)
{
var longRunningTask = SimulateLongRunningTask();
var spinner = ShowSpinner(longRunningTask);
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
private async Task ShowSpinner(Task longRunningTask)
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
LoadingText.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
LoadingText.Text = "Done!";
}
private async Task SimulateLongRunningTask()
{
await Task.Delay(TimeSpan.FromSeconds(10));
}
}
}
Here is a recording of it running, with window interaction proving that the UI is not blocked.
As an extra bonus, I got bored and implemented the extension method I mentioned (with super special bonus, a "local function" feature from C# 7!).
public static class TaskExtensions
{
public static async Task WithSpinner(this Task longRunningTask, TextBlock spinnerTextBox)
{
async Task ShowSpinner()
{
var numDots = 0;
while (!longRunningTask.IsCompleted)
{
if (numDots++ > 3) numDots = 0;
spinnerTextBox.Text = $"Waiting{new string('.', numDots)}";
await Task.Delay(TimeSpan.FromSeconds(.5));
}
spinnerTextBox.Text = "Done!";
}
var spinner = ShowSpinner();
var tasks = new List<Task>
{
longRunningTask,
spinner,
};
await Task.WhenAll(tasks);
}
}
You use it like this:
await SimulateLongRunningTask().WithSpinner(LoadingTextBlock);
If you use the await, it means that your code will wait for the async operation to finish at that line, and then continue.
That is why your progress task is not started until task task is finished.
You can create a background thread that runs in parallel with the task until it is finished and in there you can tell the UI thread to animate the dots once per second. Since the UI thread is NOT blocked (but only waiting for the task to finish), this works.
Example code:
string originalLblContent = (lblProgress.Content as string) ?? "";
bool taskStarted = false;
var progressThread = new Thread((ThreadStart)delegate
{
// this code will run in the background thread
string dots = "";
while(!taskStarted || !task.IsCompleted) {
if(dots.Length < 3) {
dots += ".";
} else {
dots = "";
}
// because we are in the background thread, we need to invoke the UI thread
// we can invoke it because your task is running asynchronously and NOT blocking the UI thread
Dispatcher.Invoke(() =>
{
lblProgress.Content = originalLblContent + dots;
});
Thread.Sleep(1000);
}
});
progressThread.Start();
taskStarted = true;
await task;
// the task is now finished, and the progressThread will also be after 1 second ...
Your approach is a little funky here. The await statements will prevent the method returning until each thread is finished. The await feature is not a completely asynchronous execution (why would it be? you have threads for that).
You need to re-think your approach to the problem. Fundamentally, you want to update the UI while another process is in progress. This calls for multithreading.
From Microsoft:
"Handling blocking operations in a graphical application can be difficult. We don’t want to call blocking methods from event handlers because the application will appear to freeze up. We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the UI thread because we can’t directly modify the GUI from our worker thread. We can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the UI thread. Eventually, these delegates will be executed with permission to modify UI elements.
In this example, we mimic a remote procedure call that retrieves a weather forecast. We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the UI thread when we’re finished."
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/threading-model

How to run codes on background thread in Windows Runtime

I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.
But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.
executing LoadDetails method takes around 3 seconds to complete.
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await Task.Run(() => LoadDetails(textBlock, item.Id));
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = count.ToString();
});
}
How to fix this so it doesn't block the UI thread? thanks.
(It's a Windows Phone Runtime app)
It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?
The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.
I would have written it something more like this:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await LoadDetails(textBlock, item.Id);
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
textBlock.Text = count.ToString();
}
I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
}
It looks like your code is correctly not blocking the UI thread by using await, but since LoadItemDetails() is presumably being called on the UI thread, it won't finish until the method is finished doing its work.
To fix this, just omit the await on the call to Task.Run(), so something like
Task.Run(() => LoadDetails(textBlock, item.Id));
should make LoadItemDetails() return immediately.

Custom ProgressBar Indicator not showing up before time consuming action

I implemented the custom progressbar indicator in my Windows Phone 8 project. It works fine if I try to toggle the indicator with a button. But of course I want it to show up while I perform time consuming actions (filling a list with many items). But as it blocks the UI the progressbar indicator doesn't show up before the action but only afterwards. I tried .UpdateLayout() on the indicator itself and the whole page before performing modifications to the list but none of it worked.
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
// add ~100 list items
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
Is there any other way to do this?
You could offload your time consuming work to a new task and add a continuation to set progress bar visibility at the end. Here i'm using the Task Parallel Library to achieve this:
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
Task.Run(() =>
{
// Do CPU intensive work
}).ContinueWith(task =>
{
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
}, TaskScheduler.FromCurrentSynchronizationContext());
You should run your heavy job asynchronously (more about async at MSDN and at the Stephen Cleary Blog) - so that it won't block UI.
The very simple example where you have a ProgressBar and a heavy Task which will inform PBar about its progress can look like this: (I've subscribed the start of the method to Button Click)
private async void StartBtn_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>( (p) =>
{
progresPB.Value = p;
});
await DoSomething(progress); // start asynchronously Task with progress indication
}
private Task<bool> DoSomething(IProgress<double> progress)
{
TaskCompletionSource<bool> taskComplete = new TaskCompletionSource<bool>();
// run your heavy task asynchronous
Task.Run(async () =>
{
for (int i = 0; i < 10; i++) // work divided into parts
{
await Task.Delay(1000); // some heavy work
progress.Report((double)i / 10);
}
taskComplete.TrySetResult(true);
});
return taskComplete.Task;
}

C#/WPF Using async and await but the UI thread is still blocked

I'm having this simple code:
private async void Button_Click_2(object sender, RoutedEventArgs e)
{
var progress = new Progress<int>();
progress.ProgressChanged += (a, b) =>
{
this.progressBar.Value = b;
};
// this is blocking
await this.LongRunOpAsync(filepath, progress);
// this is not blocking
// await this.LongRunOpAsync(filepath, null);
}
public Task LongRunOpAsync(string filename, IProgress<int> progress)
{
return Task.Run(() =>
{
using (var ops = new LongOps())
{
ops.LongRunOp(filename, progress);
}
});
}
Once I click my button the UI is still blocked from the long running operation. If I don't use the Progress and instead give my long running operation null as the second parameter the UI isn't blocking. I'm quite sure this "error" is due to some misunderstanding I have about async/await and threads.
The code you've shown won't block the UI thread.
In fact, as shown, it doesn't need async/await - so I'm assuming this is not the actual code.
You need to look at what ops.LongRunOp does with the progress function.
I suspect it marshals progress back to the UI thread - so it can access UI controls.
If it does this too often and too quickly, it will swamp the UI thread and make the app unresponsive.

c# thread and a waiting form

I connect to a webserive. While the webservice is connected i want to have a waiting form with an animated gif inside of it. The waiting form is correctly displayed but the animated give is not animated it is fixed.
Can anybody help me. I have already tried : DoEvents but the gif is still not animated.
// Create the new thread object
Thread NewThread = new Thread(new ThreadStart(RunThread));
// Start the new thread.
NewThread.Start();
// Inform everybody that the main thread is waiting
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
waitingDialog.Activate();
Application.DoEvents();
// Wait for NewThread to terminate.
NewThread.Join();
// And it's done.
waitingDialog.Close();
MessageBox.Show("Upload erfolgreich erledigt.", "Upload Erfolgreich",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
public void RunThread()
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
}
Don't call Join on a thread from within the UI thread. Instead, disable any controls you don't want to act on until the task has completed (e.g. buttons) and then call back into the UI thread when the operation has completed - so move the "And it's done" code into a new method which is invoked at the end of the operation. If you're using .NET 4, I'd suggest using the TPL for this, as it makes it easier to represent "a task which is in progress" and to add a continuation to it. (It's also a good start for what will become the idiomatic way of doing async operations in .NET 4.5.)
The problem is coming from your join. join is synchronous, so basically you are making your UI wait till the thread finishes its work.
You want to use a callback function to come back to your UI.
Edit : ive been skeetified
You problem is here:
NewThread.Join();
This blocks the UI thread until NewThread ends.
Here's one way to do it:
private myDelegate;
// ...
myDelegate = new Action(RunThread);
myDelegate.BeginInvoke(new AsyncCallback(MyCallback),null);
// You RunThread method is now running on a separate thread
// Open your wait form here
// ...
// This callback function will be called when you delegate ends
private void MyCallback(IAsyncResult ar)
{
myDelegate.EndInvoke(ar);
// Note this is still not the UI thread, so if you want to do something with the UI you'll need to do it on the UI thread.
// using either Control.Invoke (for WinForms) or Dispatcher.Invoke (for WPF)
}
Thread.Join is a blocking call that does not pump messages so that is your problem. It is typically advised to avoid calling any kind of synchronization mechanism that causes the UI thread to block.
Here is a solution using the Task class and the Invoke marshaling technique.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
Task.Factory.StartNew(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
}
Here is a solution using a raw Thread.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
var thread = new Thread(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
thread.Start();
}
C# 5.0 makes this kind of pattern even easier with its new async and await keywords1.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
await Task.Run(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
});
waitingDialog.Close();
}
1Not yet released.

Categories

Resources