Task caused UI Freezing - WPF - c#

The structure of my code is shown below:
internal async Task ButtonClickMethod()
{
await this.InitializeMethod();
}
internal async Task InitializeMethod()
{
await this.Call();
}
private async Task Call()
{
await this.CallTasks();
}
private async Task CallTasks()
{
List<Task> tasks = new List<Task>();
tasks.Add(/*time-consuming task A*/);
tasks.Add(/*time-consuming task B*/);
tasks.Add(/*time-consuming task C*/);
tasks.Add(/*time-consuming task D*/);
await Task.WhenAll(tasks);
}
Task defined as follows:
/*time-consuming task*/
internal async Task TimeConsuming()
{
// code...
await Task.Run(() =>
{
// code... A/B/C/D
});
// code...
}
Is there any problem with this code that will cause the UI to freeze?
Is the problem caused by Task.WhenAll?

Use this instead :
private async void ClickButton(object sender, RoutedEventArgs e)
{
await ThatFirstMethod(any parameters);
}
private async Task ThatFirstMethod(any parameters)
{
await Task.Run(() => {
ThatFirstTimeConsumingMethod(any param);
});
await Task.Run(() => {
ThatSecondTimeConsumingMethod(any param);
});
}
private bool ThatFirstTimeConsumingMethod(any param)
{
//code
return true;
}
.....
If you have to access UI Elements you will not be able to do so, beacause your "heavy" methods are being processed by another Thread.
Here's a solution :
private bool ThatFirstTimeConsumingMethod(any param)
{
string s = "";
Application.Current.Dispatcher.Invoke(() => {
s = myTextBox.Text;
});
// do what you want with the s variable
Application.Current.Dispatcher.Invoke(() => {
myTextBox.Text = s;
});
return true;
}
So basically, you're asking your main Thread to execute an action (where your UI is getting processed) with Application.Current.Dispatcher.Invoke. And when you're in the await Task.Run you're asking another thread to process the heavy action.

Related

Why does an async Task.Run terminate a Thread and a Task Run does not?

I am starting a Thread where an await Task.Run can be invoked.
After starting a Thread with the ThreadStart.Start method, why does the await Task.Run terminate the Thread and Task.Run does not?
Here is some code as an example:
public async Task Task1()
{
if (awaitRunTask)
{
await Task.Run(async () =>
{
await Test();
}
);
}
else
{
Task.Run(async () =>
{
await Test();
}
);
}
}
In the above example, if a Thread invokes the Task1 method, and awaitRunTask = true, the Thread terminates. If awaitRunTask = false, the Thread does not terminate.
When I say terminate, the Thread does not complete correctly and the method where the ThreadStart.Start is invoked returns. This happens at the await Test() code.
Why is this and if I want to await a Task.Run on a Thread, is there a way to do this?
EDIT
Here is some code to show a more detailed example:
public class ThreadExample
{
public bool awaitRunTask;
public string value;
private async Task StartThreadAsync()
{
var method = this.GetType().GetMethod("RunTasksAsync");
ThreadStart threadStart;
threadStart = async () =>
{
await InvokeAsync(method, this, null);
};
var thread = new Thread(threadStart);
thread.Start();
thread.Join();
}
public async Task RunTasksAsync()
{
await Task1Async();
Task2();
}
private async Task Task1Async()
{
if (awaitRunTask)
{
await Task.Run(async () =>
{
await TestAsync();
}
);
}
else
{
Task.Run(async () =>
{
await TestAsync();
}
);
}
}
private void Task2()
{
value = "valid";
}
private async Task TestAsync()
{
await Task.Delay(1000);
}
private async Task InvokeAsync(MethodInfo method, object instance, object[] parameters)
{
dynamic awaitable = method.Invoke(instance, parameters);
await awaitable;
}
public async Task ValueIsCorrectAsync()
{
value = "not valid";
awaitRunTask = false;
await StartThreadAsync();
var isCorrect = (value == "valid");
}
public async Task ValueIsNotCorrectAsync()
{
value = "not valid";
awaitRunTask = true;
await StartThreadAsync();
var isCorrect = (value == "valid");
}
}
The ValueIsCorrectAsync method works correctly as the Task2 method sets the value field.
The ValueIsNotCorrectAsync method does not work correctly as the await Task.Run in the Task1Async method interferes with the Thread. The StartThreadAsync method returns before the Task2 method sets the value field.
The only difference between the two methods, is the value of awaitRunTask.
How should I change my code such that the value field is set correctly when awaitRunTask = true?
EDIT3
If the await Task.Delay(1000); is commented out in the TestAsync method, the code works for both awaitRunTask = true and awaitRunTask = false;
Can someone please explain to me why? I need to know why because the TestAsync method needs to be able to run asynchronous code.
Why is this?
As I explain on my blog, await here is actually returning to its caller. So the Task1 method returns an incomplete task to its caller, presumably the thread's main method, which presumably is async void. When the thread's main method returns (due to it's await), the thread exits.
The core of the problem is that the Thread type doesn't understand or work naturally with asynchronous code. Thread is really a very low-level building block at this point and is best avoided in modern code. There are very few scenarios where it can't be replaced with Task.Run.
if I want to await a Task.Run on a Thread, is there a way to do this?
The easiest solution is to get rid of the legacy thread completely; replace it with Task.Run.
Otherwise, you need the thread to block. If the continuations can run on thread pool threads, then you can just block directly (e.g., GetAwaiter().GetResult()). If the continuations need to run on that thread, then use AsyncContext from my AsyncEx library.
Here's a minimal version of your sample code:
async Task Main()
{
var te = new ThreadExample();
await te.StartThreadAsync(false);
await te.StartThreadAsync(true);
}
public class ThreadExample
{
public string value;
public async Task StartThreadAsync(bool awaitRunTask)
{
value = "not valid";
var thread = new Thread(() => Task1Async(awaitRunTask));
thread.Start();
thread.Join();
var isCorrect = (value == "valid");
Console.WriteLine(isCorrect);
}
private async Task Task1Async(bool awaitRunTask)
{
if (awaitRunTask)
{
await Task.Run(async () => await Task.Delay(1000));
}
value = "valid";
}
}
This outputs:
True
False
The thread that enters Task1Async executes the line value = "valid" when awaitRunTask == false, but when it's true it hits the await Task.Run and, because of the async state machine, the thread returns to the caller at this point and executes the thread.Join().
Effectively you've created an extreme race condition.

How to wait on task that runs async method

I'm writing WPF app and recently started working with await/async so the GUI thread does not perform any time consuming operations.
My problem is I want to load two collections from db asynchronously using Entity framework. I know I can't call two ToListAsync() methods on DbContext so I wanted to use tasks.
I wrote async method LoadData() that should wait on completing the LoadNotifications() and then call LoadCustomers().
But when the execution gets to await this.context.MailingDeliveryNotifications.ToListAsync(); it creates another task and somehow it doesn't care about the task.Wait() in my LoadData() method, so it calls LoadCustomers() before completing the first call on DbContext.
The code:
public async void LoadData()
{
Task task = this.LoadNotifications();
task.Wait();
await this.LoadCustomers();
}
private Task LoadNotifications()
{
return Task.Run(() => this.LoadNotificationsAsync());
}
private async void LoadNotificationsAsync()
{
List<MailingDeliveryNotification> res = await this.context.MailingDeliveryNotifications.ToListAsync();
this.Notifications = new ObservableCollection<MailingDeliveryNotification>(res);
}
private Task LoadCustomers()
{
return Task.Run(() => this.LoadNotificationsAsync());
}
private async void LoadCustomersAsync()
{
List<Customer> res = await this.context.Customers.ToListAsync();
this.Customers = new ObservableCollection<Customer>(res);
}
I know I can solve this using this code
public async void LoadData()
{
List<MailingDeliveryNotification> res = await this.context.MailingDeliveryNotifications.ToListAsync();
this.Notifications = new ObservableCollection<MailingDeliveryNotification>(res);
List<Customer> res2 = await this.context.Customers.ToListAsync();
this.Customers = new ObservableCollection<Customer>(res2);
}
but when I will need to add another collection to load from db, this method will grow to much. I want to keep my code Clean.
Simplify your code:
public async Task LoadDataAsync()
{
await LoadNotificationsAsync();
await LoadCustomersAsync();
}
private async Task LoadNotificationsAsync()
{
var res = await context.MailingDeliveryNotifications.ToListAsync();
Notifications = new ObservableCollection<MailingDeliveryNotification>(res);
}
private async Task LoadCustomersAsync()
{
var res = await context.Customers.ToListAsync();
Customers = new ObservableCollection<Customer>(res);
}
Or probably just:
public async Task LoadDataAsync()
{
Notifications = new ObservableCollection<MailingDeliveryNotification>(
await context.MailingDeliveryNotifications.ToListAsync());
Customers = new ObservableCollection<Customer>(
await context.Customers.ToListAsync());
}

Refactor to remove Taks.Run from my code

I just wrote the following code
public void Save()
{
while (this.IsAsyncInProcess)
Thread.Sleep(100);
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}
public async Task LoadAsync()
{
this.IsAsyncInProcess = true;
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
this.IsAsyncInProcess = false;
}
Now I had a classical deadlock because, after this.orders completed, it would wait for the gui thread to resume in order to set this.IsAsyncInProcess to false. However, the gui thread was busy inside Save()
Now I refactored LoadAsync to
public async Task LoadAsync()
{
await Task.Run(async () =>
{
this.IsAsyncInProcess = true;
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
this.IsAsyncInProcess = false;
});
}
I can't just refactor Save to SaveAsync for compability reasons.
Is there a better way to achive this, without using Task.Run?
You can use async locking. If you can't change the signiture of Save then you can just proxy to an async local function.
static SemaphoreSlim sem = new SemaphoreSlim(1,1);
public void Save()
{
SaveAsync();
public async Task SaveAsync()
{
await sem.WaitAsync();
try{
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}finally{
sem.Release();
}
}
}
public async Task LoadAsync()
{
await sem.WaitAsync();
try{
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
}finally{
sem.Release();
}
}
or even better create your own async lock
static SemaphoreSlim sem = new SemaphoreSlim(1,1);
public static async Task<IDisposable> LockAsync(){
await sem.WaitAsync();
return Disposable.Create(()=>sem.Release());
}
public void Save()
{
SaveAsync();
public async Task SaveAsync()
{
using(await LockAsync()){
this.customer.OrderCount = this.orders.Count();
this.customer.OrderTotal = this.orders.Sum(o => x.Total);
this.customerRepo.Save();
}
}
}
public async Task LoadAsync()
{
using(await LockAsync()){
this.customer = await this.customerRepo.GetCustomerAsync(...);
this.orders = await this.customerRepo.GetOrdersAsync(...);
}
}

C# Use async/await on UdpClient Receive

The following code uses Task to receive asyncronously and shows the received result in the console:
private void ReceiveMessage()
{
Task.Run(async() =>
{
using(var udpClient = new UdpClient(15000))
{
while(true)
{
var receivedResult = await udpClient.ReceiveAsync();
Console.Write(Encoding.ASCII.GetString(receivedResult.Buffer));
}
}
});
}
I want to learn how to use async/await functions so I would like to know how to make the function ReceiveMessage() asynchronously by using async/await?
If you want the whole method to be awaitable, simply change it to that:
private async Task ReceiveMessage()
{
using(var udpClient = new UdpClient(15000))
{
while(true)
{
var receivedResult = await udpClient.ReceiveAsync();
Console.Write(Encoding.ASCII.GetString(receivedResult.Buffer));
}
}
}
You don't need Task.Run() anymore, which would use a thread. That thread is not needed. The method now returns to the caller while awaiting ReceiveAsync().
When ReceiveAsync() finishes, the method is (eventually) resumed at Console.WriteLine().
The code you have is valid if you want it to be fire and forget, listening on a separate thread. If you not want this, I would remove the Task.Run and be sure to return a Task in your method, like this:
private async Task ReceiveMessage()
{
using (var udpClient = new UdpClient(15000))
{
while (true)
{
var receivedResult = await udpClient.ReceiveAsync();
Console.Write(Encoding.ASCII.GetString(receivedResult.Buffer));
}
}
}
Simply add another async/await to your function:
private async void receiveMessage()
{
await Task.Run(async() =>
{
using(var udpClient = new UdpClient(15000))
{
while(true)
{
var receivedResult = await udpClient.ReceiveAsync();
Console.Write(Encoding.ASCII.GetString(receivedResult.Buffer));
}
}
});
}
If you want to write a method being awaitable, return a task:
private Task foo(){
Task doStuff = new Task(() => {});
doStuff.Start();
return doStuff;
};
//Another method
private async void bar()
{
await foo();
}
Update:
As mentioned below, you do really not need the execution of that task on a thread inside the thread pool. It's not wrong, but useless. For better usage, you can use:
private async void receiveMessage()
{
using(var udpClient = new UdpClient(15000))
{
while(true)
{
var receivedResult = await udpClient.ReceiveAsync();
Console.Write(Encoding.ASCII.GetString(receivedResult.Buffer));
}
}
}

Why isn't Task.WhenAll working?

Here's some code I'm working with, that fires of three tasks. My Stop is called, and from the output, it looks as though the tasks finish after Stop returns, which isn't what I'd expect.
This is Visual Studio 2013 with .NET 4.5.1
I tried the same using AutoResetEvent which worked as expected.
Here's the output from this:
Three tasks without auto reset
Starting...
FIRST
SECOND
THIRD
Running...
FIRST
FIRST
FIRST
SECOND
FIRST
FIRST
THIRD
...
SECOND
FIRST
Stopping...
THIRD
All done!
First done
Second done
Third done
What I'd expect to see is:
First done
Second done
Third done
All done!
Here's the code (I even added locking around the Console output, but it made no difference):
public class ThreeTasksWithoutAutoResetEvent
{
private CancellationTokenSource cancellation;
private Task[] tasks;
private object obj = new object();
public ThreeTasksWithoutAutoResetEvent()
{
Console.WriteLine("Three tasks without auto reset");
cancellation = new CancellationTokenSource();
tasks = new Task[3];
Message("Starting...");
Start();
Message("Running...");
Thread.Sleep(3000);
Message("Stopping...");
Stop();
Message("All done!");
}
private void Start()
{
tasks[0] = this.First(cancellation.Token);
tasks[1] = this.Second(cancellation.Token);
tasks[2] = this.Third(cancellation.Token);
}
private async void Stop()
{
cancellation.Cancel();
await Task.WhenAll(tasks);
}
private async Task First(CancellationToken token)
{
await Task.Run(
() =>
{
while (!token.IsCancellationRequested)
{
Message("FIRST");
Thread.Sleep(100);
}
Message("First done");
}, token);
}
private async Task Second(CancellationToken token)
{
await Task.Run(
() =>
{
while (!token.IsCancellationRequested)
{
Message("SECOND");
Thread.Sleep(300);
}
Message("Second done");
}, token);
}
private async Task Third(CancellationToken token)
{
await Task.Run(
() =>
{
while (!token.IsCancellationRequested)
{
Message("THIRD");
Thread.Sleep(500);
}
Message("Third done");
}, token);
}
private void Message(string message)
{
lock (obj)
{
Console.WriteLine(message);
}
}
}
Because you're not waiting for Stop method to finish. You can't wait for an async void method. You need return a Task, so that the caller can wait/await for it to complete.
private Task Stop()
{
cancellation.Cancel();
return Task.WhenAll(tasks);
}
Then you can call Stop().Wait(); instead of Stop. That will wait for WhenAll to complete.

Categories