In my application I have multiple tabs, displaying data from a database with Entity Framework 5.
When I switch between the tabs I start auto loading the data via a Task, because I don't want the GUI to become unresponsive (this task takes about 5-10 seconds):
public async void LoadData()
{
[...]
await Task.Run(
() =>
{
Measurements = DataContext.Measurements
.Where(m => m.MeasureDate = DateTime.Today)
.ToList();
});
[...]
}
But while the task runs the user still can change to another tab, and if he does that I would like to cancel the EF query and/or the Task.
What would be the best way to accomplish this?
In EF5, there is no way to cancel the queries since it doesn't accept CancellationToken. You can read more about this here: entity framework cancel long running query
However, EF6 does support it.
It has async version of all methods. So ToList() could instead be ToListAsync() for long running queries and it does have support for CancellationToken.
// ***Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public async void LoadData()
{
// ***Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
await Task.Run(
() =>
{
Measurements = DataContext.Measurements
.Where(m => m.MeasureDate = DateTime.Today)
.ToList();
}, cts);
}
//I dont know what front end you using but in WPF for example on the tab event
<TabControl SelectionChanged="OnSelectionChanged" ... />
private void OnSelectionChanged(Object sender, SelectionChangedEventArgs args)
{
TabItem item = sender as TabItem; //The sender is a type of TabItem...
if (item != null)
{
if (cts != null)
{
//This cancels the current long Running task.
cts.Cancel();
//call for next tab with filter LoadData(filter);
}
}
}
My personal view is. The best way to go about doing this is to load all the data for your tabs upfront if you can. And then render the view. So when you click between tabs the data is already loaded. Plus the cost of calling the database only affects you once. Instead of roundtrips to the database everytime you click on a tab.
Related
I have a form with a button and a list box. I want to add to the list box the results from two functions. These two functions can take an unknown amount of time to complete and I want to execute them simultaneously. As soon as either of the functions completes the computation, I want to display the result in the list box (before the other function completed). At the moment, the results are displayed after both functions complete. I wouldn't mind if the functions update the list box themselves.
async Task<string> LongTaskAsync()
{
for(int i = 0; i < 50; i++) {
Thread.Sleep(100);
}
return "Completed long task async";
}
async Task<string> ShortTaskAsync()
{
for(int i = 0; i < 5; i++) {
Thread.Sleep(100);
}
return "Completed short task async";
}
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = Task.Run(() => LongTaskAsync());
var shortTask = Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
}
The reason why it shows 2 of them at the same time related how you chain your awaits.
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
You are awaiting longer task before the shorter one. The second line runs after long task done its work in this time shorter one was already completed that's why you see them at the same time. But in a world you don't know what task will take longer to execute you need to have a better solution.
Action<Task<string>> continuationFunction = t => { this.listBox1.Items.Add(t.Result); };
Task.Run(() => LongTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
Task.Run(() => ShortTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
TaskScheduler.FromCurrentSynchronizationContext() is for to avoid cross thread access exceptions.
You don't have to use ContinueWith for this. It's almost always possible to avoid mixing async/await and ContinueWith-style of continuations. In your case, it can be done like this:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
async Task longTaskHelperAsync() {
// probably, Task.Run is redundant here,
// could just do: var item = await LongTaskAsync();
var item = await Task.Run(() => LongTaskAsync());
listBox1.Items.Add(item);
}
async Task shortTaskHelperAsync() {
// probably, Task.Run is redundant here, too
var item = await Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(item);
}
await Task.WhenAll(longTaskHelperAsync(), shortTaskHelperAsync());
}
I believe this way it's more readable and you don't have to worry about synchronization context, FromCurrentSynchronizationContext, etc.
Also, most likely you'd want to take care of possible re-entrancy, if BtnRunClickis clicked again while those async ctasks are still in-flight.
You could solve it a bit more generically by creating a method that awaits a task, and also adds the result of the task to the ListBox.
async Task ProcessAndAddToListAsync(Func<Task<string>> function)
{
var value = await Task.Run(function); // Start the task in a background thread
listBox1.Items.Add(value); // Update the control in the UI thread
}
Then use this method inside the event handler of the button-click event:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = ProcessAndAddToListAsync(LongTaskAsync);
var shortTask = ProcessAndAddToListAsync(ShortTaskAsync);
await Task.WhenAll(longTask, shortTask); // optional
// Here do anything that depends on both tasks being completed
}
I want to process items one by one, every item from a datagrid element.
I have a method which creates a background worker:
internal void Run(Action doWork, Action completed, Action loadingBeforeAction = null, Action loadingAfterAction = null)
{
using (BackgroundWorker worker = new BackgroundWorker())
{
worker.DoWork += (s, ev) =>
{
if (loadingBeforeAction != null)
{
_dispatcher.Invoke(loadingBeforeAction);
}
doWork();
};
worker.RunWorkerCompleted += (s, ev) =>
{
if (loadingAfterAction != null)
{
_dispatcher.Invoke(loadingAfterAction);
}
completed();
};
worker.RunWorkerAsync();
}
}
And now process the selected items from datagrid:
var folders = btn.Name.Equals("test")
? _model.Folders.ToArray()
: fileDataGrid.SelectedItems.Cast<FolderStatus>().ToArray();"
and
foreach (var folder in folders)
{
Run(() =>
{
Dispatcher.Invoke(()=> {_model.Message = $"This is message for {folder.Name}"});
// long operation here
}, () =>
{
// stuff
}, () =>
{
_model.IsBusy = true;
}, () =>
{
_model.IsBusy = false;
});
}
Seems that all items are processed simultaneously and process message flick from text to other depends on _model.Message text.
How to process item one by one but without blocking the UI?
BackgroundWorker is a legacy component from Windows Forms. You shouldn't use it with WPF applications. It will work, but there are better ways to implement what you want.
The easiest way is to use the TAP (Task-based Asynchronous Programming) pattern, supported by the async and await C# keywords.
If you're targeting at least the .NET Framework 4.5 or .NET Core, you have everything out-of-the box. For .NET Framework 4.0, there are NuGet packages to enable this functionality.
The implementation could look like this:
async void MyButtonClick(object sender, RoutedEventArgs e)
{
var btn = (Button)sender;
var folders = btn.Name.Equals("test")
? _model.Folders.ToArray()
: fileDataGrid.SelectedItems.Cast<FolderStatus>().ToArray();"
foreach (var folder in folders)
{
// This will be executed on the UI thread
_model.Message = $"This is message for {folder.Name}"};
_model.IsBusy = true;
// This will be executed on a worker thread due to Task.Run
await Task.Run(() => YourLongRunningOperation(folder));
await Task.Run(() => SomeOtherLongRunningOperation());
// This will be executed again on the UI thread
_model.IsBusy = false;
}
}
A couple of notes:
The method above has a signature async void (object, EventArgs). However, async methods should return a Task or a Task<T>. async void is here to make the event handler async. This is the only case when you should use async void - UI event handlers. Note that an uncaught exception in an async void UI event handler will crash your application.
When await Task.Run() returns, the thread context will be restored to the original one. So, your _model.IsBusy = false code will be executed on the UI thread again. If you don't need this, use await Task.Run().ConfigureAwait(false).
Read more about the Task-based asynchronous pattern (TAP).
Here is a short but nice FAQ and tutorial too.
Thanks in advance.
I want to use a foreach to add items to a `GridView . This is done in an async method.
I want to retrieve the items from other async methods and display them in a GridView:
public async Task SetGridItems(CancellationToken ct, /* and some items */)
{
GridItems.clear();
//get two list of item I want
var ListA = await getBmethodAsync().AsTask(ct);
var ListB = await getAmethodAsync().AsTask(ct);
foreach (itemA A in ListA)
{
GridItem i = new GridItem();
//set name
i.name = A.Name;
//get icon
i.image = img;
GridItems.Add(i);
}
foreach (ItemB b in ListB)
{
GridItem i = new GridItem();
i.name = b.Name;
i.image.SetSource(icon);
GridItems.Add(i);
}
}
The content is simplified for convenience.
When I run this method in a button click handler:
private async void check_btn2_Click(object sender, RoutedEventArgs e)
{
if (cts != null) {
cts.Cancel();
debug.Text = "cancel a job 4";
}
cts = new CancellationTokenSource();
try
{
await SetGridItems(ct.Token, /* and some items */).AsAsyncAction().AsTask(cts.Token);
}
catch (OperationCanceledException) { debug.Text = "cancel a job"; }
}
Here is the problem:
If I click this button twice (clicking fast):
- on the first click, the event callback will be called and my task will start to run
- something will show in the GridView but will not complete (it ends at 80%)
- on second click, the GridView is cleared as expected, and some new content is loaded but the GriViewis only showing the last 20% of the first click task
So, why does the second click not cancelling the first task ?
I've searched for a long time on net, nut haven't found anything. Please help and try to give some ideas how to achieve this.
I am not good at English and thanks
I see two problems here:
First as Vincent said in the comment, you're passing the cancellation token in a little redundant way with the AsAsyncAction().AsTask(cts.Token); in the button click handler and the .AsTask(ct); in the method itself.
Second, and much more important, you're passing the cancellation token to the task, but you're never using it in the method. Passing a cancellation token to a task is mostly used in parallel, not async work. It's a way for you to coordinate and query the execution state of multiple tasks running at once. And anyway, its always dependent on the usage of the token itself inside the executing code. Think of it this way, you're telling the task object itself that you're cancelling the operation, but your code doesn't know how to handle it.
In async development, you don't really need to pass the cancellation to the task object, since you don't have to coordinate the state of many of them, you're only executing the one. You should pass the token to your method, and let your code handle it.
So inside your SetGridItems method, try doing something like this:
public async Task SetGridItems(CancellationToken ct, /* and some items */)
{
GridItems.clear();
//get two list of item I want
var ListA = await getBmethodAsync().AsTask(ct);
var ListB = await getAmethodAsync().AsTask(ct);
foreach (itemA A in ListA)
{
ct.ThrowIfCancellationRequested();
GridItem i = new GridItem();
//set name
i.name = A.Name;
//get icon
i.image = img;
GridItems.Add(i);
}
foreach (ItemB b in ListB)
{
ct.ThrowIfCancellationRequested();
GridItem i = new GridItem();
i.name = b.Name;
i.image.SetSource(icon);
GridItems.Add(i);
}
}
Make sure to do the same in the GetXMethodAsync methods. This way your code knows how to handle the cancellation. Because right now, the task object may be cancelled, but it still keeps executing the code, since it doesn't know where to stop.
For more on task cancellation you can see the following links:
1. https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation
2. https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children
Hope this helps
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
I want to update my a DataGrid from multiple thread in WPF(c#). I use dataGrid.Dispatcher.BeginInvoke() and dataGrid.Dispatcher.Invoke() but they freeze program (main thread). How can update dataGrid from multiple threads with a timeout ( because I use web service that may be unreachable ).
Use a Task kick off the web service request asynchronously. To do this you will probably need to convert the EAP (event-based asynchronous pattern) style into a TAP (task-based asynchronous pattern) style. Here is how you do that.
private Task<IEnumerable<YourDataItem>> CallWebServiceAsync()
{
var tcs = new TaskCompletionSource();
var service = new YourServiceClient();
service.SomeOperationCompleted +=
(sender, args) =>
{
if (args.Error == null)
{
tcs.SetResult(args.Result);
}
else
{
tcs.SetException(args.Error);
}
};
service.SomeOperationAsync();
return tcs.Task;
}
After you have that in place then you can use the new async and await keywords to make the call and wait for it to return using continuation style semantics. It would look like this.
private async void Page_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
IEnumerable<YourDataItem> data = await CallWebServiceAsync();
YourDataGrid.DataSource = data;
}
That is it! It does not get a whole lot more elegant than that. This will perform the operation asynchronously on a background thread and then bind the results to the DataGrid on the UI thread.
If the WCF service is unreachable then it will throw an exception and will be attached to the Task so that it propagates up to the await call. At that point it will be injected into the execution and can be wrapped with a try-catch if necessary.
If you don't need the DataGrid editing to be done in the threads, you can run them in the main thread like this:
this.Invoke((Action)delegate
{
//Edit the DataGrid however you like in here
});
Make sure to only put things you need to be run in the main thread inside it (otherwise that would defeat the purpose of multithreading).