Task.Run then Invoke on main thread alternative using async await ContinueWith? - c#

The following code works perfectly. It shows the spinner on the UI, starts a task using a thread from the threadpool and runs the heavy operation, once complete, logic to hide the spinner executes on the main thread as intended.
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
Device.BeginInvokeOnMainThread(() =>
{
// Update UI to hide spinner
this.LoadingCustomers = false;
});
});
}
My question; Is there a better way to write this logic using ContinueWith/ConfigureAwait options? Using these options seems to block the UI thread. In the example below, shouldn't the UI thread continue running the UI logic (animating the spinner/user input) and then come back to complete the logic inside the ContinueWith?
public void LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
this.custService.GetCustomers().ContinueWith((t) =>
{
var customers = t.Result;
// code truncated for clarity
// Update UI to hide spinner
this.LoadingCustomers = false;
});
}
As requested in the comments, here is the code for GetCustomers. the dbContext is EntityFrameworkCore.
public async Task<List<CustomerModel>> GetCustomers()
{
return await this.dbContext.Customers.ToListAsync();
}
UPDATE
The answer by FCin is correct, however; the cause root of this seems to be with EFCore and ToListAsync, it isn't running asynchronously.

Proper way of writing such method is to use async/await from start to finish. Right now you are doing fire and forget meaning if there is exception inside Task.Run you will never know about it. You should start from an event handler. This can be whatever, mouse click, page loaded, etc.
private async void MouseEvent_Click(object sender, EventArgs args)
{
await LoadCustomers();
}
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
// We don't need Device.BeginInvokeOnMainThread, because await automatically
// goes back to calling thread when it is finished
var customers = await this.custService.GetCustomers();
this.LoadingCustomers = false;
}
There is an easy way to remember when to use Task.Run. Use Task.Run only when you do something CPU bound, such as calculating digits of PI.

EDIT: #bradley-uffner suggested to just write the following:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
var customers = await this.custService.GetCustomers();
// code truncated for clarity
// you are still on UI thread here
this.LoadingCustomers = false;
}
How about this:
public async Task LoadCustomers()
{
// Update UI to show spinner
this.LoadingCustomers = true;
await Task.Run(async () =>
{
var customers = await this.custService.GetCustomers();
// code truncated for clarity
});
this.LoadingCustomers = false;
}
The code after await is executed on the current thread so it should work out of the box.

Related

C# WPF - How to load data to datagrid in thread

I have a problem with application freezes for a few seconds.
I loading data from XML file and deserialize to MyList.
public List<My20FieldsDataRecord> MyList;
...
void ShowDataInThread()
{
MyGrid.DataContext = MyList;
}
public void ShowDane(bool inThread)
{
if (inThread)
{
Thread thr = new Thread(ShowDataInThread);
thr.Start();
}
else
{
ShowDataInThread();
}
}
if inThread = false everything work fine, but application not responding for a 2-3 seconds.
When inThread = true application crash.
I want do this in thread, but i was not able to understand that how it works from examples on internet. I'll be very grateful for your help, becouse i have no idea how to do that.
Since Microsoft introduced the async / wait approach for .NET Framework programming in .NET 4.5, the code for async methods is a lot.
You can not find any async async with as example as such as type:
private async void button1_Click(object sender, EventArgs e)
{
string result = await AnMethodAsync();
textBox1.Text += result;
}
private Task<string> AnMethodAsync()
{
//Do somethine async
}
And you think this is done, the function will run async do not have to worry about hanging thead anymore, too strong.
But the problem is not so simple.
Now try to put in the AnMethodAsync () function the following code:
Thread.Sleep(5000);
return Task.FromResult("HoanHT");
Run the code above and when you press button1, the UI will hang stiff for 5s.
What the hell, I have applied async / await properly that the UI is still hanging.
After a brief look at the problem: In the AnMethodAsync function does not create any other task on the other thread. The consequence is that asyn away, but it still runs on UI thread -> UI freeze.
Then fix it, there are two ways:
Method 1: Create a new task and executable:
return Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
return "HoanHT";
});
Method 2: Use Task.Delay () instead of Thread.Sleep ()
private async Task<string> AnMethodAsync()
{
await Task.Delay(5000);
return "HoanHT";
}
This is the way I usually do with the asynchronous problem, plus one more way is to use ConfigureAwait () for the Task but will cause minor problems.
Here is a way that I've found to load data for a datagrid in the background while not blocking your UI.
First, create a lock object, and enable collection synchronization, then actually load the data on a background thread using Task.Run():
private readonly object _myDataLock = new object();
private FastObservableCollection<My20FieldsDataRecord> MyList = new FastObservableCollection<My20FieldsDataRecord>();
private CollectionViewSource MyListCollectionView = new CollectionViewSource();
public MyViewModelConstructor() : base()
{
// Other ctor code
// ...
// assign the data source of the collection views
MyListCollectionView.Source = MyList;
// Setup synchronization
BindingOperations.EnableCollectionSynchronization(MyList, _myDataLock);
}
private async void LoadMyList()
{
// load the list
await Task.Run(async () =>
{
MyList.ReplaceAll(await MyRepository.LoadMyList());
}
);
}
Then in your repository you could write:
public virtual async Task<IList<My20FieldsDataRecord>> LoadMyList()
{
var results = await this.DataContext.TwentyFieldDataRecords
.OrderByDescending(x => x.MyDate).ToListAsync().ConfigureAwait(false);
return results;
}
Then you could bind in your associated view like this:
<controls:DataGrid Name="MyListDataGrid" Grid.Row="1"
....
ItemsSource="{Binding MyListCollectionView.View}"
... >
For details, please see:
https://blog.stephencleary.com/2014/04/a-tour-of-task-part-0-overview.html
http://blog.stephencleary.com/2012/02/async-and-await.html#avoiding-context
https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.7);k(DevLang-csharp)&rd=true
https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

Update UI before actual execution of method

I want a loading indicator to start immediately before the execution of a method. The execution of the method involves the work of entity framework so I don't (can't) put that type of code in a new thread bc entity framework isn't thread safe. So basically in the method below, I want the first line to execute and have the UI update and then come back and execute the rest of the code. Any ideas?
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh();
}
I can't do this:
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
await Task.Factory.StartNew(() =>
{
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh(); //Load from entityframework
});
//This isn't good to do entityframework in another thread. It breaks.
}
You can invoke empty delegate on UI dispatcher with priority set to Render, so that UI process all the queued operations with equal or higher priority than Render. (UI redraws on Render dispatcher priority)
public async void LoadWizard()
{
IsLoading = true; //Need the UI to update immediately
App.Current.Dispatcher.Invoke((Action)(() => { }), DispatcherPriority.Render);
//Now lets run the rest (This may take a couple seconds)
StartWizard();
Refresh();
}
Assuming your busy indicator visibility is bound to IsLoading property, you are doing "something" wrong in StartWizard or Refresh method. Your StartWizard and Refresh methods should only load data from your data source. You must not have any code that changes the state of UI in your loading methods. Here is some pseudocode..
public async void LoadWizard()
{
IsLoading = true;
StartWizard();
var efData = Refresh();
IsLoading = false;
//update values of properties bound to the view
PropertyBoundToView1 = efData.Prop1;
PropertyBoundToView2 = efData.Prop2;
}
public void StartWizard()
{
//do something with data that are not bound to the view
}
public MyData Refresh()
{
return context.Set<MyData>().FirstOrDefault();
}

Using await with Task.Run but UI still hangs for a few seconds?

I am using SAP .NET Connector 3.0 and trying to login on using a separate thread so I can have the UI displaying a kind of login animation.
I am using Async and Await to start the login but the UI hangs for about 10 seconds during login.
Here is the code, its rough because I am quickly drafting a program.
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Busy.Visibility = System.Windows.Visibility.Visible; // Shows progress animation
if (await SAPLogin()) // Waits for login to finish, will always be true at the moment
{
await GetData(); // does things with sap
Busy.Visibility = System.Windows.Visibility.Collapsed; // Hides progress animation
}
}
private Task<bool> SAPLogin()
{
bool LoggedIn = true;
return Task.Run(() =>
{
Backend = new BackendConfig();
RfcDestinationManager.RegisterDestinationConfiguration(Backend);
SapRfcDestination = RfcDestinationManager.GetDestination(MyServer); // MyServer is just a string containing sever name
SapRap = SapRfcDestination.Repository;
BapiMD04 = SapRap.CreateFunction("MD_STOCK_REQUIREMENTS_LIST_API");
BapiMD04.SetValue("WERKS", "140");
return LoggedIn;
});
}
I can only imagine that something in the Task is using the UI?
EDIT 1:
Sorry forgot to explain what GetData() does.
GetData() runs various reports in SAP (lots of code). Visually, I know when its there because my little login animation will change from "Logging In" to "Grabbing Data".
When I see the UI hang I see it is during the "Logging In" phase. The login animation has a simple circle spinning. This stops part way through the login and then continues after about 5 seconds.
EDIT 2:
The hanging seems to occour at this line here
SapRfcDestination = RfcDestinationManager.GetDestination(MyServer);
EDIT 3:
Added a Photo of the threads when pausing application at the point where I see the UI hang.
Presumably, nothing inside GetData or the Task.Run lambda inside SAPLogin is trying to callback the UI thread with Dispatcher.Invoke, Dispatcher.BeginInvoke or Dispatcher.InvokeAsync. Check for such possibility first.
Then, try changing your code like below. Note how Task.Factory.StartNew with TaskCreationOptions.LongRunning is used instead of Task.Run and how GetData is offloaded (despite it's already async, so mind .Unwrap() here). If that helps, try each change independently, to see which one particularly helped, or whether it was a combination of both.
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Busy.Visibility = System.Windows.Visibility.Visible; // Shows progress animation
if (await SAPLogin()) // Waits for login to finish, will always be true at the moment
{
//await GetData(); // does things with sap
await Task.Factory.StartNew(() => GetData(),
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default).Unwrap();
Busy.Visibility = System.Windows.Visibility.Collapsed; // Hides progress animation
}
}
private Task<bool> SAPLogin()
{
bool LoggedIn = true;
return Task.Factory.StartNew(() =>
{
Backend = new BackendConfig();
RfcDestinationManager.RegisterDestinationConfiguration(Backend);
SapRfcDestination = RfcDestinationManager.GetDestination(MyServer); // MyServer is just a string containing sever name
SapRap = SapRfcDestination.Repository;
BapiMD04 = SapRap.CreateFunction("MD_STOCK_REQUIREMENTS_LIST_API");
BapiMD04.SetValue("WERKS", "140");
return LoggedIn;
},
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}

How do I update the GUI on the parent form when I retrieve the value from a Task?

I think I'm missing something obvious here, but how do I update the GUI when using a task and retrieving the value? (I'm trying to use await/async instead of BackgroundWorker)
On my control the user has clicked a button that will do something that takes time. I want to alert the parent form so it can show some progress:
private void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
In my parent form I'm listening to WorkStarted and WorkComplete to update the status bar:
myControl.WorkStarting += (o, args) =>
{
Invoke((MethodInvoker) delegate
{
toolStripProgressBar1.Visible = true;
toolStripStatusLabel1.Text = "Busy";
});
};
Correct me if I'm wrong, but the app is hanging because "Invoke" is waiting for the GUI thread to become available which it won't until my "ButtonClicked()" call is complete. So we have a deadlock.
What's the correct way to approach this?
You're blocking the UI thread Task.Result blocks until the task is completed.
Try this.
private async void ButtonClicked()
{
var task = Task<bool>.Factory.StartNew(() =>
{
WorkStarted(this, new EventArgs());
Thread.Sleep(5000);
WorkComplete(this, null);
return true;
});
await task;//Wait Asynchronously
if (task.Result) MessageBox.Show("Success!");//this line causes app to block
}
You can use Task.Run to execute code on a background thread. The Task-based Asynchronous Pattern specifies a pattern for progress updates, which looks like this:
private async void ButtonClicked()
{
var progress = new Progress<int>(update =>
{
// Apply "update" to the UI
});
var result = await Task.Run(() => DoWork(progress));
if (result) MessageBox.Show("Success!");
}
private static bool DoWork(IProgress<int> progress)
{
for (int i = 0; i != 5; ++i)
{
if (progress != null)
progress.Report(i);
Thread.Sleep(1000);
}
return true;
}
If you are targeting .NET 4.0, then you can use Microsoft.Bcl.Async; in that case, you would have to use TaskEx.Run instead of Task.Run. I explain on my blog why you shouldn't use Task.Factory.StartNew.

How do I force a task to run on the UI thread?

Original message below. Let me try and explain with more details why I am asking for this.
I have a page that listens to the Share charm request:
void Page_Loaded(object sender, RoutedEventArgs e)
{
m_transferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.GetForCurrentView();
m_transferManager.DataRequested += TransferManager_DataRequested;
}
When the event fires (TransferManager_DataRequested) it does not fire on the UI thread:
void TransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var data = args.Request.Data;
// More related stuff omitted - not important.
data.SetDataProvider(StandardDataFormats.Bitmap, GetImage_DelayRenderer);
}
Now, when GetImage_DelayRender is called, it also does not get called on the UI thread. However, in it, I need to do a bunch of UI related things. Specifically, I need to call a method that only works on the UI (it's a method I use elsewhere and I want to reuse it's logic). The method is called GetImageAsync and it needs to run on the UI because it does a bunch of interactions with WriteableBitmap. It also does a bunch of async operations (such as writing to stream etc) which is why it's async. I block the UI on GetImageAsync() for as short a time as I can.
Here's what GetImage_DelayRender looks like:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
await Dispatcher.RunTask(async () => // RunTask() is an extension method - described in the original question below.
{
try
{
var bitmapStream = await GetImageAsync();
request.SetData(RandomAccessStreamReference.CreateFromStream(bitmapStream));
}
catch
{
}
});
deferral.Complete();
}
What I want to know is, what is the most correct way to achieve the call to Dispatcher.RunTask() above (which is my hack extension method).
----- START ORIGINAL MESSAGE -------
Say I have the following task:
private async Task SomeTask()
{
await Task.Delay(1000);
// Do some UI and other stuff that may also be async
}
Edit (Clarification): I do not want to block the UI. The task I want to execute (even in the example, if you read it) WILL NOT block the UI. I just want the task to run in the context of the UI for it's synchronous portions.
I want to run this on code on the UI thread as an Async operation. Dispatcher.RunXXX() methods take an action, which means they will run the action and notify you when they are done. That's not good enough. I need the entire task to run on the UI thread (as it would have executed had I run it from the UI thread) and then, when done, to notify me back.
The only way I could think of, is to use the Dispatcher.RunXXX() methods to execute an anon delegate that sets a local variable in my method to the task and then awaits that...
public async static Task RunTask(this CoreDispatcher dispatcher, Func<Task> taskGiver)
{
Task task = null;
await dispatcher.RunAsync(() => task = taskGiver());
await task;
}
This looks pretty damn ugly. Is there a better way of doing it?
Edit2: Guys - read this code - if I execute the first code block above using the RunTask() hack I have, IT WILL NOT BLOCK THE UI on the Task.Delay()...
I want to run this on code on the UI thread as an Async operation.
Then just run it:
async void MyEventHandler(object sender, ...)
{
await SomeTask();
}
Update:
I'm not sure this is a "legal" operation, but you can schedule that method to run on the UI thread by capturing the CoreDispatcher while the UI is active and later calling RunAsync:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
Task task = null;
await coreDispatcher.RunAsync(() => { task = SomeTask(); });
await task;
deferral.Complete();
}
I don't have time to do a complete solution, so hopefully you will still find this useful...
First, as others have pointed out, you cannot run something on the UI thread and not have it block the UI thread. End of discussion. What you are saying you need is something to run on a non-UI thread and periodically notify the UI thread that there are updates that need to be processed.
To accomplish this, you need something like this...
public class LongTask
{
public event EventHandler MyEvent;
public void Execute()
{
var task = Task.Factory.StartNew(() =>
{
while (true)
{
// condition met to notify UI
if (MyEvent != null)
MyEvent(this, null);
}
});
}
}
In your UI then, do something like...
private void button_Click(object sender, RoutedEventArgs e)
{
var test = new LongTask();
test.MyEvent += test_MyEvent;
test.Execute();
}
void test_MyEvent(object sender, EventArgs e)
{
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
test.Text += " bang ";
});
You could obviously implement this in a much cleaner fashion using something like MVVM, but this is the basic idea.
}
I've done it like this:
public static Task<string> GetResultAsync()
{
return Task<string>.Factory.StartNew(() => GetResultSync());
}
In UI:
private async void test()
{
string result = await GetResultAsync();
// update UI no problem
textbox.Text = result;
}

Categories