I'm reallt trying to get my head around async/await and Tasks. I'm trying to get one method down so that I can use it throughout my program. Basically I have my own version of BusyIndicator that I want to display whilst work is being done. Here is a basic example;
private async void OnPageLoad(object sender, RoutedEventArgs e)
{
var waitWindow = new PleaseWaitWindow();
waitWindow.Show();
await LoadCompanyContracts();
waitWindow.Close();
}
private async Task LoadCompanyContracts()
{
await Task.Run(() =>
{
Dispatcher.Invoke(() =>
{
//Work done here
});
});
}
Even through these two methods and attempting implementing Tasks my BusyIndicator still doesn't rotate, it is displayed but is frozen so in effect I believe the UI thread is still blocked. How can I modify this piece of code to ensure that all CPU bound work is not blocking the UI thread?
As a general rule, you shouldn't use Dispatcher at all. It's extremely common practice, but it's a commonly bad practice.
private async Task LoadCompanyContracts()
{
await Task.Run(() =>
{
//Work done here
});
// Update UI if necessary.
}
Don't do you work inside Dispatcher.Invoke(), because your dispatcher is associated with ui thread. Do your work inside Task and use dispatcher only for updating UI
private async Task LoadCompanyContracts()
{
await Task.Run(() =>
{
/*Work done here
*
*/
Dispatcher.Invoke(() =>
{
//Update UI
});
});
}
After looking for some examples in my code here a solution you can try :
private async void OnPageLoad(object sender, RoutedEventArgs e)
{
var waitWindow = new PleaseWaitWindow();
waitWindow.Show();
InitLoadCompanyContracts();
waitWindow.Close();
}
private async void InitLoadCompanyContracts(){
await LoadCompanyContracts();
}
private async Task LoadCompanyContracts()
{
await Task.Run(() =>
{
Dispatcher.Invoke(() =>
{
//Work done here
});
});
}
I use this to have an awaitable method but out of all "graphics" methods. I don't know if is the best way to do that, but for me it's work.
Related
I have a WPF app running on .net 6 and an external device connected to it.
Initializing the device sometimes fails and I don't want to hold the UI thread trying to initialize it.
I want to run the following method (_device.Init()) in an async fashion and when it returns true, run Start() method.
edit: run it until it returns true from the _device.Init() method, not true for finishing the task
Is there a built-in functionality to do it with tasks? or any other "best practice" way?
Thank you :)
SomeDevice _device = new();
public async void Init()
{
// some other code
while (Task.Run(() => _device.Init()).Result == false)
{
}
Start();
}
public void Start()
{
// some other code
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_device.Start();
}));
}
Instead of getting the Result of the Task (which may block the UI thread) you should await the Task:
public async void Init()
{
// some other code
while (!await Task.Run(() => _device.Init()))
{
}
Start();
}
The method should also be awaitable and be awaited when called, e.g. in an async Loaded event handler:
public async Task Init()
{
// some other code
while (!await Task.Run(() => _device.Init()))
{
}
Start();
}
...
await Init();
public async void Init()
{
var task = _device.Init();
//do work here
await task;
Start();
}
Should do the trick, it'll do the work and then wait for the task to complete before going to Start();
If you want to simply wait for init to finish and then run start it's even simpler with
await _device.Init().ContinueWith((x) => { Start();})
In my application i have class PrimaryViewModel (abstract) and every ViewModel inherit from it.
To get data every X seconds i have written following virtual method
protected virtual async void RunSynchronizeTask()
{
await Task.Run(() =>
{
while (true)
{
Thread.Sleep(RefreshTime);
if (DateTime.Now.TimeOfDay - LastSyncrhonization.TimeOfDay > RefreshTime)
{
Application.Current.Dispatcher.Invoke(delegate
{
GetDataAndRefreshUI();
});
LastSyncrhonization = DateTime.Now;
}
}
});
}
So every viewModel has it owns method and it own task which will get data from DB and refresh binded controls (for example messageViewModel refresh messages list every 0.5 seconds)
but problem is that when i run released application (no debug mode, just build as release and open exe) and close, the app will close, but this async task is still working (i see it in task manager)
What should i do?
How to assign this task to something, i cannot do :
var task = await.Task.Run(...)
If i could i would create some static list with referencses to theses tasks and on application close i would kill them or sth...
You go from an async void to a Task.Run() , which is doubling up.
And then you do the actual work in a Dispatcher.Invoke()
The only thing that is truly async (concurrent) here is the Thread.Sleep().
So you can replace all this with a DispatcherTimer. That should cure your halting problem too.
Thanks Fabian!
works great with cancellation token :
protected virtual async void RunSynchronizeTask()
{
var cancelationToken = new CancellationToken();
App.TaskToDisposeTokens.Add(cancelationToken);
await Task.Run(() =>
{
try {
while (true)
{
Thread.Sleep(RefreshTime);
if (DateTime.Now.TimeOfDay - LastSyncrhonization.TimeOfDay > RefreshTime)
{
Application.Current.Dispatcher.Invoke(delegate
{
GetDataAndRefreshUI();
});
LastSyncrhonization = DateTime.Now;
}
}
}
catch(OperationCanceledException) { }
}, cancelationToken);
}
App.Cs
private void Application_Exit(object sender, ExitEventArgs e)
{
foreach(var token in TaskToDisposeTokens)
{
token.ThrowIfCancellationRequested();
}
}
and in App.Xaml
<Application x:Class="RandevouWpfClient.App"
...
Exit="Application_Exit">
I know how to use tasks and such to get it to work without async/await, but all the examples and video's ive watched i still can't figure it out. Some people have it done by Creating a task in the button click and then awaiting that task?
So far:
private async void button1_Click(object sender, EventArgs e) {
var vari = await GetId();
comboBox1.Items.Add(vari);
}
private Task<string> GetId() {
return Task.Run(() => {
return getstring();
});
}
public string getstring() {
Thread.Sleep(5000);//simulate long task
string d = "Example";
return d;
}
I've tried it a couple different ways, Is this the correct way of doing it? is there a way I can eliminate the running of another task in the GetId() method, and just return a string there?
is there a way I can eliminate the running of another task in the GetId() method, and just return a string there?
Task.Run just executes the operation on a new thread. If you don't do it and call GetId directly, it will just run on the UI thread, blocking the UI for 5 seconds.
Just marking a method async doesn't make it run on a new thread, it only means that the method uses await. If you did this:
private async void button1_Click(object sender, EventArgs e) {
var vari = await GetId();
comboBox1.Items.Add(vari);
}
private async Task<string> GetId() {
Thread.Sleep(5000);//simulate long task
string d = "Example";
return d;
}
it would compile (with a warning because GetId doesn't use await), but it would execute synchronously on the UI thread.
What you could do, however, is replace Thread.Sleep (which is synchronous) with Task.Delay (which is asynchronous):
private async void button1_Click(object sender, EventArgs e) {
var vari = await GetId();
comboBox1.Items.Add(vari);
}
private async Task<string> GetId() {
await Task.Delay(5000); //simulate long task
string d = "Example";
return d;
}
In this case, there is no new thread involved. In the background, Task.Delay just sets a kind of timer that will execute the rest of the method (following the await) when the delay is elapsed. The UI thread will be free to do something else in the meanwhile.
For example, I need to use CoreDispatcher for refreshing MVVM properties in the UI Thread.
private void ButtonClick(object sender, RoutedEventArgs e)
{
//Code not compile without keyword async
var dispatcherResult = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
//This method contains awaitable code
await _scanner.ScanAsync();
}
);
dispatcherResult.Completed = new AsyncActionCompletedHandler(TaskInitializationCompleted);
}
private void TaskInitializationCompleted (IAsyncAction action, AsyncStatus status )
{
//Do something...
}
I am expect, then TaskInitializationCompleted handler will fire AFTER ScanAsync method completed, but it fire immediatly after Dispatcher.RunAsync method started and also BEFORE then ScanAsync was completed.
How I can check to really handle async Dispatcher work completed or cancelled?
Instead of registering to the Completed event, you can await RunAsync (Because DispatcherOperation is an awaitable) which will guarantee any code runs only after completion the invocations completion:
private async void ButtonClick(object sender, RoutedEventArgs e)
{
var dispatcherResult = await this.Dispatcher
.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
await _scanner.ScanAsync();
});
// Do something after `RunAsync` completed
}
I have the following code in a WinForms application with one button and one label:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
});
}
}
}
This is a simplified version of the real application I'm working on. I was under the impression that by using async/await in my Task.Run I could set the label1.Text property. However, when running this code I get the error that I'm not on the UI thread and I can't access the control.
Why can't I access the label control?
When you use Task.Run(), you're saing that you don't want the code to run on the current context, so that's exactly what happens.
But there is no need to use Task.Run() in your code. Correctly written async methods won't block the current thread, so you can use them from the UI thread directly. If you do that, await will make sure the method resumes back on the UI thread.
This means that if you write your code like this, it will work:
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
Try this:
replace
label1.Text = "test";
with
SetLabel1Text("test");
and add the following to your class:
private void SetLabel1Text(string text)
{
if (InvokeRequired)
{
Invoke((Action<string>)SetLabel1Text, text);
return;
}
label1.Text = text;
}
The InvokeRequired returns true if you are NOT on the UI thread. The Invoke() method takes the delegate and parameters, switches to the UI thread and then calls the method recursively. You return after the Invoke() call because the method has already been called recursively prior to the Invoke() returning. If you happen to be on the UI thread when the method is called, the InvokeRequired is false and the assignment is performed directly.
Try this
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
label1.Text = "test";
}
Or
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
Or
private async Task Run()
{
var task = Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
var continuation = task.ContinueWith(antecedent=> label1.Text = "test",TaskScheduler.FromCurrentSynchronizationContext());
await task;//I think await here is redundant
}
async/await doesn't guarantee that it will run in UI thread. await will capture the current SynchronizationContext and continues execution with the captured context once the task completed.
So in your case you have a nested await which is inside Task.Run hence second await will capture the context which is not going to be UiSynchronizationContext because it is being executed by WorkerThread from ThreadPool.
Does this answers your question?
Why do you use Task.Run? that start a new worker thread (cpu bound), and it causes your problem.
you should probably just do that:
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
await ensure you will continue on the same context except if you use .ConfigureAwait(false);
Because it's on a different thread and cross-thread calls aren't allowed.
You will need to pass on the "context" to the thread you are starting. See an example here: http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/
I am going to give you my latest answer that I have given for async understanding.
The solution is as you know that when you are calling async method you need to run as a task.
Here is a quick console app code that you can use for your reference, it will make it easy for you to understand the concept.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
Console.WriteLine("Starting Send Mail Async Task");
Task task = new Task(SendMessage);
task.Start();
Console.WriteLine("Update Database");
UpdateDatabase();
while (true)
{
// dummy wait for background send mail.
if (task.Status == TaskStatus.RanToCompletion)
{
break;
}
}
}
public static async void SendMessage()
{
// Calls to TaskOfTResult_MethodAsync
Task<bool> returnedTaskTResult = MailSenderAsync();
bool result = await returnedTaskTResult;
if (result)
{
UpdateDatabase();
}
Console.WriteLine("Mail Sent!");
}
private static void UpdateDatabase()
{
for (var i = 1; i < 1000; i++) ;
Console.WriteLine("Database Updated!");
}
private static async Task<bool> MailSenderAsync()
{
Console.WriteLine("Send Mail Start.");
for (var i = 1; i < 1000000000; i++) ;
return true;
}
}
Here I am trying to initiate task called send mail. Interim I want to update database, while the background is performing send mail task.
Once the database update has happened, it is waiting for the send mail task to be completed. However, with this approach it is quite clear that I can run task at the background and still proceed with original (main) thread.