How to run codes on background thread in Windows Runtime - c#

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.

Related

Run Async/Await Task in BackgroundWorker's RunWorkerCompleted

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

async/await running on single thread

I have a console application where in some instances a user interface needs to be presented. This user interface needs to remain responsive as it will contain a loading gif, progress bar, cancel button etc. I have the following sample code:
class Program
{
static void Main(string[] args)
{
DoWork().GetAwaiter().GetResult();
}
private static async Task DoWork()
{
TestForm form = new TestForm();
form.Show();
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form.Close();
}
}
}
I would expect from the code above for the TestForm to be displayed for approximately 5 seconds before being closed due to the value of the string being "Plop", however all that happens is the Task is run and the if statement is never reached. Furthermore the UI of the TestForm does not remain responsive. What is wrong with this code?
So I've managed to hack together a dirty solution for this. It is not a clean solution so I'm still open to suggestions but for what I need it works fine
private static void DoWork()
{
TestForm form = new TestForm();
Task formTask = Task.Run(() => form.ShowDialog());
Task<string> testTask = Task.Run(() =>
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine("Background task finished");
return "Plop";
});
Console.WriteLine("Waiting for background task");
testTask.Wait();
if (testTask.Result == "Plop")
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
}
Console.WriteLine("App finished");
}
This outputs 'Waiting for background task' first, followed by the number count of the Task and then outputs 'Background task finished' when the long process is complete, as well as closes the responsive UI form
Its a classic deadlock.When your code hit await ,control goes back to main thread which is a blocking wait for DoWork GetResult(); When Task.Run thread is finished controls tries to go back to main thread but its waiting for DoWork to be finished. That is the reason last If statement never executes.
But apart from deadlock ,there is also one more issue in your code which will make your UI freeze.Its the form.Show() method.If you remove everything related to async-await and only use form ,it will still freeze.The problem is Show method expects a windows message loop which will be provided if you create a Windows.Forms application but here you are launching form from console application which doesnt have a message loop. One solution would be to use form.ShowDialog which will create its own message loop. Another solution is to use System.Windows.Forms.Application.Run method which provides a win messages loop to the form created through thread pool thread. I can give you one possible solution here but its up to you how you structure your code as the root cause is identified.
static void Main(string[] args)
{
TestForm form = new TestForm();
form.Load += Form_Load;
Application.Run(form);
}
private static async void Form_Load(object sender, EventArgs e)
{
var form = sender as Form;
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form?.Close();
}
}
Ok I did mark my first answer to be deleted, since what I put there works for WPF and not for you require, BUT in this one is doing what you asked, I did try it and opens the WinForm then closes after 5 seconds, here is the code:
static void Main(string[] args)
{
MethodToRun();
}
private static async void MethodToRun()
{
var windowToOpen = new TestForm();
var stringValue = String.Empty;
Task.Run(new Action(() =>
{
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
windowToOpen.Show();
}).Wait();
System.Threading.Thread.Sleep(5000);
stringValue = "Plop";
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
if (String.Equals(stringValue, "Plop"))
{
windowToOpen.Close();
}
}).Wait();
})).Wait();
}

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

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

UI freezes when using async await

I have troubles to make my UI work using an async method. Here is a part of my code
private async void btnDoOutput_Click(object sender, RoutedEventArgs e)
{
/* Initiliaze */
groupBoxConfiguration.IsEnabled = false;
var progressIndicator = new Progress<int>();
progressIndicator.ProgressChanged += (s,value) =>
{
progressExport.Value = (double)value;
labelPercentage.Content = "Export in progress : " + value + " %";
};
/* do Work */
switch (something)
{
case 1:
await method(input, output, options, progressIndicator);
break;
default: break;
}
/* Finalization */
groupBoxConfiguration.IsEnabled = true;
}
The method is
public async static Task<string> method(string input, string output, string options, IProgress<int> progress)
{
while(something)
{
//operations on input and output
if (progress != null)
{
progress.Report(percentage);
}
}
}
When I click on my button, the UI freezes, the groupBox is still enabled, the progress is not shown until the end.
I think you are completely misunderstanding how async / await actually works. All of your code is still running on the UI thread because you don't actually tell it otherwise. This means your await on method is pointless because it's going to run synchronously anyway.
The purpose of async/await is to allow the calling code the opportunity to continue processing until it hits part of the code that requires the result of the awaitable task. So in your example, you would need to change your method body to actually return an awaitable Task
public Task method(string input, string output, string options, IProgress<int> progress)
{
return Task.Run(() => {
while(something)
{
//operations on input and output
if (progress != null)
{
progress.Report(percentage);
}
}
});
}
First of all please don't use static methods, especially static workers.
I believe the problem is you're still on your UI thread (I'm making some wild assumptions based on the code you've given). Try using Task<string>.Factory.StartNew(...) which should automatically invoke off the UI thread.
Note may need to use the dispatcher and invoke back onto the UI thread to get your Progress bar working.

Categories

Resources