I am developing metro application in c#, I am using async and await keywords to create make metro async operatons (downloading data etc.). I always show modal "Please wait" dialog. I would like to add "Cancel" button to this modal dialog to allow cancel the background operation.
But I am not sure how to cancel processing task... Is there any example how to do that?
This is the example of my task:
// Starts task
public void StartTask()
{
// show the modal dialog with progress
_progressDialog.IsOpen = true;
_progressDialog.CancelClick += ProgressDialog_CancelClick;
await ToWork();
_progressDialog.IsOpen = false;
}
// Task which takes some seconds
private async Task DoWork()
{
await DownloadData();
await ProcessData();
}
// Cancel task
private void CancelClick(object sender, RoutedEventArgs e)
{
// hide the modal dialog with progress
_progressDialog.IsOpen = false;
// TODO: Cancel task
GoBack(this, e);
}
You can decide to implement DownloadData and ProcessData such that they take a CancellationToken and pass that to them when you need to cancel.
public Task DownloadData(CancellationToken tok)
{
tok.ThrowIfCancellationRequested();//check that it hasn't been cancelled.
//while doing your task check
if (tok.IsCancellationRequested)
{
// whatever you need to clean up.
tok.ThrowIfCancellationRequested();
}
}
For usage you can create a CancellationTokenSource and pass the token to the method.
var source = new CancellationTokenSource();
await DownloadData(source.Token);
When you need to cancel you can call Cancel() on the source
source.Cancel();
Related
I have a code. The goal of this is to cancel a task with a CancellationToken, I know that it possible to do with return; in loop, but I want to do it with CancellationToken. I tried to do it, but it does not work and I have no idea why.
The task
break a task loop on dropNumber
static CancellationTokenSource cancellationTokenSource = null;
static async Task Main(string[] args)
{
cancellationTokenSource = new CancellationTokenSource();
try
{
Task.Run(() => CountLoopAsync(cancellationTokenSource.Token, 4),cancellationTokenSource.Token);
}
catch(OperationCanceledException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.White;
Console.WriteLine("Task is cancelled!");
Console.ResetColor();
}
finally
{
cancellationTokenSource.Dispose();
}
}
private static void CountLoopAsync(CancellationToken token, int dropNumber)
{
for(int i = 0; i < 10; i++)
{
Console.WriteLine(i);
if (dropNumber == i)
{
cancellationTokenSource.Cancel();
}
}
}
}
Your Task.Run it's done with await so you don't go to Cancel sentence until the task has finished. Use Task.Run without await to allow continue running and execute the Cancel
UPDATE
I think that your example if not so good because you are trying to execute all code in a sequencial way when the use of task is usually to run code in background, in an asynchronous form. Also, I think that stop with a predefined value is a non sense: in that case, change the final step in your "for" instead of the use of a token.
You don't Cancel in the task code. If you know in that code when to cancel, you simply return. The purpose of the token is allow to cancel externally to task code. And doing that, you can't control when the task finish because it depends of something external. Maybe, for example, when an user click "Cancel" button.
Usually, your counter code try to calculate all. But, been a long time operation, you give to the user the oportunity to cancel in any moment.
Encapsulate your code in a class:
public class YourClass : IDisposable
{
private CancellationTokenSource _cancellationTokenSource = null;
private Task _task = null;
public void Wait(int milliSeconds)
{
this._task.Wait(milliSeconds, this._cancellationTokenSource.Token);
}
public void Dispose()
{
this._cancellationTokenSource?.Dispose();
this._task?.Dispose();
}
public async Task RunLongOperationInBackground()
{
this._cancellationTokenSource = new CancellationTokenSource();
this._task = Task.Run(
() => CountLoopAsync(this._cancellationTokenSource.Token),
this._cancellationTokenSource.Token);
await this._task;
}
public void Abort()
{
// Cancel the operation
this._cancellationTokenSource?.Cancel();
}
private static void CountLoopAsync(CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
// Uncomment to simulate task takes some time to finish
//Thread.Sleep(3000);
// You don't know when the action will be cancelled. If you know that, you don't
// need the cancellation: you can do the for until your well known end
if (token.IsCancellationRequested)
{
break;
}
}
}
}
This class allow you run an operation (RunLongOperationInBackground) and also cancel in any moment (Abort). So, you run your task and, in any moment, you can cancel the task. If you look the CountLoopAsync code, it tries to execute all but, it checks sometimes (in each iteration in this case) the token, and if someone has request to cancel, you exit the for. But you can do whatever you want. For example, you may run always up to next hundred so, even if token has been cancelled, you may continue up to next hundred. Or if the cancellation has been requested near to the end of the operation, you may decide continue. The token only tell you that outside wants terminate.
Create a Form (instead a console) with 2 buttons, for a more realistic example:
public partial class Form1 : Form
{
private readonly YourClass _yourClass;
public Form1()
{
this.InitializeComponent();
this._yourClass = new YourClass();
}
private async void OnStartButtonClick(object sender, EventArgs e)
{
await this._yourClass.RunLongOperationInBackground();
}
private void OnCancelButtonClick(object sender, EventArgs e)
{
this._yourClass.Abort();
}
private void OnForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (this._yourClass != null)
{
// Wait, for example 30 seconds before end the appication
this._yourClass.Wait(30000);
this._yourClass.Dispose();
}
}
}
You create your class in the constructor. The Start button run your long time operation (you may want use a Delay in each for iteration to be able to cancel before terminate). In any time you can click de Abort button to cancel the operation. And in that moment, in your "for" the token tell you that has been cancelled and you exit the for.
I think that the problem is here:
cancellationTokenSource.Dispose();
It seems that the cancellationTokenSource is disposed prematurely. You are not supposed to dispose it before all associated work has completed. In your case you must probably wait for the completion of the Task.Run before calling Dispose.
Task.Run(() => CountLoopAsync(cancellationTokenSource.Token, 4),
cancellationTokenSource.Token).Wait();
I have a TreeView in a form, that is dock-filled to a groupbox. The problem to solve is, that there is an operation that is run on a Task, which loads data from a server application. When this is running, there should be a progress indicator displayed in the location of the TreeView. That is, it should be shown instead of the TreeView, and take its place fully. The following is what the code for this looks like:
private async void preload_data(object sender, System.EventArgs args)
{
try
{
// the treeView should be disabled/invisible at this point
// make the CircularProgressBar enabled/visible
// get the information from the server
await Configuration.network.fetch_stuff();
}
catch (Exception ex)
{
// something bad happened
}
finally
{
// whatever happened, the treeView should be back
}
}
The CircularProgressBar (a third-party control) should appear as in the code above, and should replace the TreeView. It should fill the exact same space as the TreeView would, which is dock-filled. Below is a screenshot of this:
This form and all its controls have been designed in the designer, and I don't want to do it there, I want to do it programmatically. What is the best way to go about this? I have looked at examples of Controls.Remove() and Controls.Add(), but it's not clear if that fits this purpose.
It is quite common to change the visual output while actions are running, like you do. Think of disabling buttons, to discourage operators to press the button again, or show something visually, to inform operators about the progress.
For simplicity, without the try-catch
private async Task PreloadDataAsync()
{
this.ShowFetchingData(true);
// start fetching data, do not await:
var taskFetchData = Configuration.network.fetch_stuff();
// while taskFetchData not completed, await some time
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
this.ShowFetchingData(false);
}
private void ShowFetchindData(bool show)
{
// disable/enable certain buttons, menu items, show progressbar?
this.ButtonFetchData.Enabled = !show;
this.MenuFetchData.Enabled = !show;
this.ProgressBarFetchData.Visible = show;
}
private bool IsFetchingData => this.ProgressBarFetchData.Visible;
private void ShowProgress(int progress)
{
this.ProgressBarFetchData.Position = progress;
}
For simplicity, I've left out checks for the position in the progress bar, but you get the gist.
Usage:
private async void OnButtonFetchData(object sender, EventArgs e)
{
await this.PreloadDataAsync();
}
Room for improvement
The problem with this is that there is no timeout at all: if FetchStuff does not complete, you are in an endless wait. The method that microsoft proposes is the use of a CancellationToken. Almost every async method has an overload with a CancellationToken. Consider creating one yourself:
// existing method:
private async Task<MyData> fetch_Stuff()
{
await this.fetch_stuff(CancellationToken.None);
}
// added method with CancellationToken
private async Task<MyData> fetch_Stuff(CancellationToken token)
{
// Call async function overloads with the token,
// Regularly check if cancellation requested
while (!token.IsCancellationRequested)
{
... // fetch some more data, without waiting too long
}
}
Instead of IsCancellationRequested, consider to throw an exception: ThrowIfCancellationRequested.
Usage:
private async Task PreloadDataAsync()
{
// preloading should be finished within 30 seconds
// let the cancellationTokenSource request cancel after 30 seconds
TimeSpan maxPreloadTime = TimeSpan.FromSeconds(30);
using (var cancellationTokenSource = new CancellationTokenSource(maxPreloadTime))
{
await PreloadDataAsync(cancellationTokenSource.Token);
}
}
The overload with CancellationToken:
private async Task PreloadDataAsync(CancellationToken token)
{
this.ShowFetchingData(true);
// execute code similar to above, use overloads that accept token:
try
{
var taskFetchData = Configuration.network.fetch_stuff(token);
TimeSpan updateTime = TimeSpan.FromSeconds(0.250);
int progressCounter = 0;
while (!taskFetchData.IsCompleted)
{
token.ThrowIfCancellationRequested();
this.ShowProgress(progressCounter);
var taskWait = Task.Delay(updateTime, token);
await Task.WhenAny(new Task[] {taskFetchData, taskWait};
// either taskFetchData.IsCompleted, or Delay time waited
++progressCounter;
}
}
catch (TaskCancelledException exc)
{
this.ReportPreloadTimeout();
}
finally
{
this.ShowFetchingData(false);
}
}
Or if you want a button that cancels the task:
private CancellationTokenSource cancellationTokenSource = null;
private book IsPreloading => this.CancellationTokenSource != null;
private async Task StartStopPreload()
{
if (!this.IsPreloading)
StartPreload();
else
CancelPreload();
}
private async Task StartPreload()
{
// preload not started yet; start it without timeout;
try
{
this.cancellationTokenSource = new CancellationTokenSource();
await PreloadDataAsync(this.cancellationTokenSource.Token);
}
catch (TaskCancelledException exc)
{
this.ReportPreloadCancelled();
}
finally
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
}
}
The method where operators can stop preloading:
private async void StopPreload()
{
this.cancellationTokenSource.Cancel();
// the method that created this source will Dispose it and assign null
}
All you have to do is create buttons / menu items to start / stop preloading
Solved using Visible properties of controls.
I want to show a loading dialog when I'm doing a UI blocking task so I did this :
public void RetrievePosteInformations(string posteNumber)
{
ApplicationDataContainer localSettings = ApplicationData.Current.LocalSettings;
//try to show dialog on UI here
RecherchePosteDialog recherchePosteDialog = new RecherchePosteDialog();
_= recherchePosteDialog.ShowAsync();
//UI blocking task
foreach(KeyValuePair<int,string> keyval in filepaths)
{
retrieveCSVInfo(keyval.Value, posteNumber, keyval.Key);
}
//after task hiding the dialog again
recherchePosteDialog.Hide();
}
But here the Dialog is not showing at all. I don't want to use an await statement because the program would keep stuck at await recherchePosteDialog.ShowAsync().
I only want to show it asynchronously and hide it when the task is finished.
You could try running it in a thread with a ManualResetEvent.
When you call
RetrieveWait.Set();
the dialog gets hidden.
Example:
private static ManualResetEvent RetrieveWait = new ManualResetEvent(false);
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
RecherchePosteDialog recherchePosteDialog = new RecherchePosteDialog();
recherchePosteDialog.ShowAsync();
RetrieveWait.WaitOne()
recherchePosteDialog.Hide();
});
I'm a quite inexperienced c# programmer and need help on managing the flow of my program. It is a WinFormApp that asks for multiple user inputs and then uses them to establish serial communication with a device in order to make measurements. The measurements are made in an async method that takes about 20 minutes to run. So I'm using
void main()
{
//Setup
}
private void button1_Click(object sender, EventArgs e)
{
await measurements();
//Plot results
}
private async Task measurements()
{
while(true){
//Make some measurements
await Task.Delay(120000);
//Make measurements, present data in the UI form.
//return if done
}
}
Now I need to make a button which enables the user to cancel the measurements in order to change some input or parameter and then restart the measurements. So I added a "Cancel"-button.
private void button7_Click(object sender, EventArgs e)
{
textBox64.Enabled = true;
button6.Enabled = true;
button5.Enabled = true;
textBox63.Enabled = true;
button3.Enabled = true;
trackBar1.Enabled = true;
timer.Enabled = true;
button7.Enabled = false;
clearData();
// measurement.Stop();
}
Now I do not know how to manage the flow of the program. I have tried to make a try-catch structure in button1_Click() and throw an Exception from button7_Click, but it doesn't get through to button1_Click().
Then I tried to run measurements() on a new thread. But the thread does not have access to 70-ish UI items on my main form.
And even I wont sink as low as trying Goto.
What I need is advice on how you would program in this situation in order to get a good control of the application and not compromise the flow of the program with risky stuff like exceptions and Goto.
If you want to cancel the actual task midway through then you need to look at using a CancellationTokenSource and passing a cancellation token into your async method.
This is the Microsoft documentation on that which has a good example near the bottom and this is another good blog on showing a progress bar and allowing cancellation. that second article has a good overview:
Cancellation is controlled by the CancellationToken structure. You
expose cancellation tokens in the signature of cancelable async
methods, enabling them to be shared between the task and caller. In
the most common case, cancellation follows this flow:
The caller creates a CancellationTokenSource object.
The caller calls a cancelable async API, and passes the
CancellationToken from the CancellationTokenSource
(CancellationTokenSource.Token).
The caller requests cancellation using the CancellationTokenSource
object (CancellationTokenSource.Cancel()).
The task acknowledges the cancellation and cancels itself, typically
using the CancellationToken.ThrowIfCancellationRequested method.
To make your app respond quickly to a cancel request you need to then check the cancellation token regularly in your long running method and respond to it accordingly if a cancellation has been requested.
This is some rough code, but it should do the trick.
Using CancellationToken.
I've used a toggle method to test if the async task is to be cancelled.
CancellationTokenSource cts;
private async button1_Click(object sender, EventArgs e)
{
toggleAsyncTask(false)
}
private void toggleAsyncTask(bool isCancelled)
{
if(cts==null)
var cts = new CancellationTokenSource();
if(!isCancelled)
{
await measurements(cts.Token);
}
else
{
cts.Cancel();
cts.Dispose();
cts = null;
}
}
private async Task measurementsOne(CancellationToken token)
{
try
{
while(true){
//Make some measurements
await Task.Delay(120000); // don't know if you need this.
//Make measurements, present data in the UI form.
//return if done
}
catch(OperationCancelledException)
{
// to do if you please.
}
}
private void button7_Click(object sender, EventArgs e)
{
// button stuff
toggleAsyncTask(true); // isCancelled is true.
}
I have a method that is called after the initialization component, the method signature is like:
public async void method_name ()
{
// code ...
}
Inside that method, I have a loop running with 4 different if statements. I need the loop to pause at each if statements and wait for the user to press a button. Since pressing that button will add info and stuff. Once the button is pressed, I want the loop to continue and of course pause at the next if statements.
I thought about doing it like await Task.Delay(30000); but if the user is done entering the info before that timer is over, he/she will just be waiting. and That's not efficient.
You can do that with TaskCompletionSource. Create it and await its Task property and when a button is clicked use it to complete that task. That allows to asynchronously wait for the user input without blocking a thread or using Task.Delay.
TaskCompletionSource<bool> _tcs;
async Task Foo()
{
// stuff
_tcs = new TaskCompletionSource<bool>();
await _tcs.Task
// other stuff
}
void ButtonClicked(object sender, EventArgs e)
{
_tcs.SetResult(false);
}