uwp C# cancel async task and re-run - c#

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

Related

Why isn't Razor/Blazor(WASM) page refreshing after update in both cases sending in two identical objects? (Cause: MudBlazor table module))

Trying to shorten things down, one button has this code (takes the first vacant slot and updates the DB as not vacant):
#onclick="#(e => UpdateTimeSlot(#FirstVacantSlot, "Booked", BookerGuid))"
and the other button has this code (user picks a vacant spot):
#onclick="#(e => UpdateTimeSlot(#selectedItem, "RowBooked", BookerGuid))"
Calling after update:
protected override async Task OnParametersSetAsync()
{
await RefreshDb(); // get list of todays timeslots from DB
StateHasChanged(); // well, Blazor magic refresh or whatever
}
FirstVacantSlot is set from a method that picks it from the DB.
selectedItem is set when clicking a row in a (MudBlazor) table.
Both get filled with the same type of data and send to the method UpdateTimeSlot.
Both buttons correctly updates the DB, but the table refreshes ONLY after use of the first button, not after the second button. Why?
Also, if I click the second button and then the first button the update no longer works there either.
Things I tried:
Debugged to see that the objects have identical values populated. Trying to follow whats happening.
StateHasChanged() in every corner of the code.
await this.OnParametersSetAsync() (Which makes the first button work)
Add; The UpdateTimeSlot method:
I substituted a call to RefreshDb() with the OnParameterAsync(). Still can't understand why it beahves differently depending on which button I click.
private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
var updateTimeSlot = new TimeSlotDto();
updateTimeSlot.ID = (Guid)vacantSlot.ID;
updateTimeSlot.TimeSlotStart = vacantSlot.TimeSlotStart;
updateTimeSlot.TimeSlotEnd = vacantSlot.TimeSlotEnd;
updateTimeSlot.IsVacant = false;
updateTimeSlot.CreatedUTC = vacantSlot.CreatedUTC;
updateTimeSlot.Title = title;
updateTimeSlot.UpdatedUTC = DateTime.UtcNow;
updateTimeSlot.BookerId = bookerId;
await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);
//await this.OnInitializedAsync();
await this.OnParametersSetAsync();
}
UPDATE
The problem has been located to the MudBlazor component Table, which isn't updating correctly after selecting a row in the table itself. Everything else is working as it should; I tried a foreach-loop to iterate through the IENumerable currentTimeSlots, that the MudTable also is dependent on and the foreach-loop updated correctly. So I guess it's a completely different question now.
Update - RefreshDb()
private async Task RefreshDb()
{
var roomClient = new RoomClient();
room = await roomClient.GetRoomByIdAndDateAsync(ThisRoomId, dtToday);
allTimeSlotsOfToday = room.TimeSlots;
currentTimeSlots = allTimeSlotsOfToday.GetAllCurrentTimeSlots(dtToday);
HasItems = currentTimeSlots.Count();
FirstVacantSlot = allTimeSlotsOfToday.GetFirstVacant(dtToday);
AnyVacantSlot = allTimeSlotsOfToday.Count(f => f.IsVacant);
}
Update:
For some this might help:
MudBlazor MudTable issue on GitHub
A bit of a trick to get the thread released is to add a Task.Delay. You shouldn't need to call OnParametersSetAsync (seems a bit kludgy).
Change your onclicks to something like:
#onclick="#(async () => await UpdateTimeSlotAsync(#FirstVacantSlot, "Booked", BookerGuid))"
Then an async handler:
private async Task UpdateTimeSlotAsync(...)
{
// update slot code...
await RefreshDb(); // get list of todays timeslots from DB
StateHasChanged(); // well, Blazor magic refresh or whatever
await Task.Delay(1); // let UI refresh
}
Firstly, never call OnInitialized{Async} or OnParametersSet{Async} manually. They are part of the ComponentBase infrastructure.
Try this:
You're calling await RefreshDb(); which I assume contains the code to do the refresh.
Update OnParametersSetAsync to:
protected override async Task OnParametersSetAsync()
{
await RefreshDb(); // get list of todays timeslots from DB
// StateHasChanged(); not required if RefreshDb is coded correctly.
}
UpdateTimeSlot should look like this:
private async Task UpdateTimeSlot(TimeSlotDto vacantSlot, string title, Guid bookerId)
{
....
await _http.PutAsJsonAsync($"https://localhost:44315/api/v1/TimeSlot/Update?id={updateTimeSlot.ID}", updateTimeSlot);
await RefreshDb();
// StateHasChanged(); if this fixes the problem then RefreshDb is not coded correctly.
}
You need to ensure RefreshDB is a proper async method that returns a Task that can be awaited, and any sub method calls are also awaited correctly. If a call to StateHasChanged at the end of UpdateTimeSlot fixes the problem then RefreshDb has an await problem.
Update your question with the RefreshDb code if necessary.
Mudtable is not updating it's content when you are in EditingMode. I could solve my StateHasChanged Issues with calling
mudTable.SetEditingItem(null);

Form is displaying before async function completes

I've got a WinForms project that scans a given network and returns valid IP addresses. Once all the addresses are found, I create a user control for each and place it on the form. My functions to ping ip addresses use async and Task which I thought would "wait" to execute before doing something else, but it doesn't. My form shows up blank, then within 5 seconds, all the user controls appear on the form.
Declarations:
private List<string> networkComputers = new List<string>();
Here's the Form_Load event:
private async void MainForm_Load(object sender, EventArgs e)
{
//Load network computers.
await LoadNetworkComputers();
LoadWidgets();
}
The LoadNetworkComputers function is here:
private async Task LoadNetworkComputers()
{
try
{
if (SplashScreenManager.Default == null)
{
SplashScreenManager.ShowForm(this, typeof(LoadingForm), false, true, false);
SplashScreenManager.Default.SetWaitFormCaption("Finding computers");
}
else
Utilities.SetSplashFormText(SplashForm.SplashScreenCommand.SetLabel, "Scanning network for computers. This may take several minutes...");
networkComputers = await GetNetworkComputers();
}
catch (Exception e)
{
MessageBox.Show(e.Message + Environment.NewLine + e.InnerException);
}
finally
{
//Close "loading" window.
SplashScreenManager.CloseForm(false);
}
}
And the last 2 functions:
private async Task<List<string>> GetNetworkComputers()
{
networkComputers.Clear();
List<string> ipAddresses = new List<string>();
List<string> computersFound = new List<string>();
for (int i = StartIPRange; i <= EndIPRange; i++)
ipAddresses.Add(IPBase + i.ToString());
List<PingReply> replies = await PingAsync(ipAddresses);
foreach(var reply in replies)
{
if (reply.Status == IPStatus.Success)
computersFound.Add(reply.Address.ToString());
}
return computersFound;
}
private async Task<List<PingReply>> PingAsync(List<string> theListOfIPs)
{
var tasks = theListOfIPs.Select(ip => new Ping().SendPingAsync(ip, 2000));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
I'm really stuck on why the form is being displayed before the code in the MainForm_Load event finishes.
EDIT
I forgot to mention that in the LoadNetworkComputers it loads a splash form which lets the user know that the app is running. It's when the form shows up behind that, that I'm trying to avoid. Here's a screenshot (sensitive info has been blacked out):
The reason one would use async-await is to enable callers of functions to continue executing code whenever your function has to wait for something.
The nice thing is that this will keep your UI responsive, even if the awaitable function is not finished. For instance if you would have a button that would LoadNetworkComputers and LoadWidgets you would be glad that during this relatively long action your window would still be repainted.
Since you've defined your Mainform_Loadas async, you've expressed that you want your UI to continue without waiting for the result of LoadNetWorkComputers.
In this interview with Eric Lippert (search in the middle for async-await) async-await is compared with a a cook making dinner. Whenever the cook finds that he has to wait for the bread to toast, he starts looking around to see if he can do something else, and starts doing it. After a while when the bread is toasted he continues preparing the toasted bread.
By keeping the form-load async, your form is able to show itself, and even show an indication that the network computers are being loaded.
An even nicer method would be to create a simple startup-dialog that informs the operator that the program is busy loading network computers. The async form-load of this startup-dialog could do the action and close the form when finished.
public class MyStartupForm
{
public List<string> LoadedNetworkComputers {get; private set;}
private async OnFormLoad()
{
// start doing the things async.
// keep the UI responsive so it can inform the operator
var taskLoadComputers = LoadNetworkComputers();
var taskLoadWidgets = LoadWidgets();
// while loading the Computers and Widgets: inform the operator
// what the program is doing:
this.InformOperator();
// Now I have nothing to do, so let's await for both tasks to complete
await Task.WhenAll(new Task[] {taskLoadComputers, taskLoadWidgets});
// remember the result of loading the network computers:
this.LoadedNetworkComputers = taskLoadComputers.Result;
// Close myself; my creator will continue:
this.Close();
}
}
And your main form:
private void MainForm_Load(object sender, EventArgs e)
{
// show the startup form to load the network computers and the widgets
// while loading the operator is informed
// the form closes itself when done
using (var form = new MyStartupForm())
{
form.ShowDialog(this);
// fetch the loadedNetworkComputers from the form
var loadedNetworkComputers = form.LoadedNetworkComputers;
this.Process(loadedNetworkComputers);
}
}
Now while loading, instead of your mainform the StartupForm is shown while the items are loaded.. The operator is informed why the main form is not showing yet. As soon as loading is finished, the StartupForm closes itself and loading of the main form continues
My form shows up blank, then within 5 seconds, all the user controls appear on the form.
This is by design. When the UI framework asks your app to display a form, it must do so immediately.
To resolve this, you'll need to decide what you want your app to look like while the async work is going on, initialize to that state on startup, and then update the UI when the async work completes. Spinners and loading pages are a common choice.

Move method execution to separate thread and receive return value

I am rather new to multithreading and the likes and still trying to wrap my head around the whole thing.
I have the following scenario (simplified):
public partial class Form1 : Form
{
private AppLogic logic = new AppLogic();
private void GetData()
{
var dataSetNames = logic.GetDataSetNames();
foreach (var dataSetName in dataSetNames)
{
var page = new TabPage();
var dgv = new DataGridView { Dock = DockStyle.Fill, DataSource = logic.GetDataSet(dataSetName) }
page.Controls.Add(dgv);
tabControl1.TabPages.Add(page)
}
}
private void GetMeSomeDataToolStripMenuItem_Click(object sender, EventArgs e)
{
GetData();
}
/* ... */
}
That's fancy and all but since the part where logic gathers data takes a few seconds, the Form will always freeze. I don't want that so I am currently trying to get rid of that. What I tried was changing the GetData() call a bit:
Task<TabControl> t = Task<TabControl>.Factory.StartNew(GetData);
tabControl1 = t.Result;
Of course I adjusted GetData() accordingly so it now returns a new TabControl instead of accessing the Form directly.
This didn't improve my situation at all, though, which is probably because accessing the Result Property of a Task forces the accessing Task to wait for completion.
So I am currently looking for a different way to do this but I can't come up with anything.
I guess the best way would be to use ContinueWith:
t.ContinueWith(formerTask => {
if (formerTask.IsFaulted) return;
var x = formerTask.Result;
// do whatever but use Invoke if necessary
})
This starts a task to process the result after your task has completed.
Another way to deal with this is to make the eventhandler async:
private async void GetMeSomeDataToolStripMenuItem_Click(object sender, EventArgs e)
{
TabControl t = await Task<TabControl>.Run(GetData);
tabControl1 = t;
}
Note that the async void pattern should generaly be avoided but is Ok for eventhandlers.
This approach is much easier when you need to update your GUI with the returned results. Creating a TabControl on another thread is highly suspect though. You should separate the data-getting and the control-creating. The latter action should be done on the main Thread.
This will unfreeze your GUI but to make this really efficient the GetData method should be made async, and the task.Run replaced by awaiting an I/O method.
Alright, after some work I found out dryman's answer wasn't optimal for me. This is what I did:
private async void GetData()
{
tabControl1.TabPages.Clear();
Task<List<string>> dataSetNames = Task<List<string>>.Factory.StartNew(logic.GetDataSetNames);
await dataSetNames;
foreach (var dataSetName in dataSetNames.Result)
{
Task<DataTable> sourceTable = Task<DataTable>.Factory.StartNew(() => logic.GetDataSet(dataSetName));
TabPage page = new TabPage { Name = dataSetName }
DataGridView dgv = new DataGridView { Dock = DockStyle.Fill }
dgv.DataSource = await sourceTable;
page.Controls.Add(dgv);
tabControl1.TabPages.Add(page);
}
}
This even makes my code more DRY, since I don't have to write
Task<TabControl> t = Task<TabControl>.Factory.StartNew(GetData);
everytime I want to call GetData().
TL;DR: use await threadName; before accessing the Result-property of a thread if you don't want to block your accessing thread.

Trying to make method wait for internal event handling finished

I'm new in C# async/await and facing some issues while trying to work with async method.
I have a collection:
private IList<IContactInfo> _contactInfoList
And an async method:
public async Task<IList<IContactInfo>> SelectContacts()
{
_contactInfoList = new List<IContactInfo>();
ContactsSelector selector = new ContactsSelector();
selector.ShowPicker();
selector.ContactsSelected += (object sender, ContactsSelectorEventArgs e) =>
{
this._contactInfoList = e.Contacts;
};
return _contactInfoList;
}
Contact selector is a popup user control which allows to select some contacts from phone and after the "OK" button tapped it fires ContactsSelected event. I need to get the selected contacts list from the event arguments e.Contacts and return that list in above mentioned SelectContacts() async method. And here is the issue: My method is already returning empty list _contactInfoList before the ContactsSelected event has finished his job. I know that async/await even doesn't matter in this case and this issue will be exist in usual method, but I just need to make that method to wait event handling result.
What you need to do here is convert an event style of asynchronous programming to a task style of asynchronous programming. The use of a TaskCompletionSource make this fairly straightforward.
public static Task<IList<IContactInfo>> WhenContactsSelected(
this ContactsSelector selector)
{
var tcs = new TaskCompletionSource<IList<IContactInfo>>();
selector.ContactsSelected += (object sender, ContactsSelectorEventArgs e) =>
{
tcs.TrySetResult(e.Contacts);
};
return tcs.Task;
}
Now that we have a method that returns a task with the result that we need, the method that uses it is quite straightforward:
public Task<IList<IContactInfo>> SelectContacts()
{
ContactsSelector selector = new ContactsSelector();
selector.ShowPicker();
return selector.WhenContactsSelected();
}
There are a few things to note here. First, I removed the instance field; that seems like a bad idea here. If SelectContacts is called several times it would result in the two fighting over that field. Logically if you do need to store the list it should be a local variable. Next, there are no await uses here, so the method shouldn't be marked as async. If you wanted to await the call to WhenContactsSelected then feel free to add async back in, but as of now I see no real need for it.

EF 5: How to cancel a longrunning query in a async Task

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.

Categories