Best approach for cancel an awaitable method before navigation? - c#

I am working in Xamarin app, but I think my question is more focused in .NET framework and C#.
For example I navigate to PageOne, and in constructor is called the asynchronous method InitializePageOneData()...
public PageOne()
{
await InitializePageOneData()
}
But just in this moment, while it is waiting for the method execution, I navigate to a second page (PageTwo), which has another asynchronous operations, but I see that the InitializePageOneData() method does not stops his execution.
My goal is to stop this asynchronous operation before doing the navigation to another page. What is your recommendation?
Note: in the asynchronous operation, I am using TaskCompletionSource:
private Task<Response> ProcessRequest(Request request)
{
tsc = new TaskCompletionSource<Response>();
eventHandler = (s, e) =>
{
try
{
_client.ProcessRequestsCompleted -= eventHandler;
tsc.TrySetResult(e.Result);
}
catch (Exception ex)
{
_client.ProcessRequestsCompleted -= eventHandler;
tsc.TrySetException(ex);
}
};
_client.ProcessRequestsCompleted += eventHandler;
_client.ProcessRequestsAsync(request);
return tsc.Task;
}

My goal is to stop this asynchronous operation before doing the
navigation to another page. What is your recommendation?
Pass down a CancellationToken to your async method, and monitor on that token. If the user wants to navigate away to page two, use the CancellationTokenSource to cancel that operation.
An example would be:
private CancellationTokenSource cts = new CancellationTokenSource();
public async Task LoadPageOneAsync()
{
try
{
await InitializePageOneDataAsync(cts.Token)
}
catch (OperationCanceledException e)
{
// Handle if needed.
}
}
public async Task InitializePageOneDataAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var something in collection)
{
cancellationToken.ThrowIfCancellationRequested();
// Do other stuff
}
}
When you want to cancel, invoke cts.Cancel() from PageTwo:
public async Task LoadPageTwoAsync()
{
cts.Cancel();
cts = new CancellationTokenSource();
await LoadSecondPageAsync();
}

Related

background task is blocking main thread in wpf .net core application

I have a background task in my software which should run indefinitely.
The code repeating the task (and possibly other tasks in the future) looks like this:
public class RunTicketTasks
{
public async Task RunBackgroundTasks()
{
AssignTickets assignTickets = new AssignTickets();
while (true)
{
await assignTickets.AssignTicketCreator();
await Task.Delay(5 * 60 * 1000);
}
}
}
At the same time I have the WPF UI MainWindow.xaml which is the application entry point as far as I know.
within public MainWindow I start the task like the following way:
public MainWindow()
{
InitializeComponent();
JiraBackgroundTasks.RunTicketTasks ticketTasks = new JiraBackgroundTasks.RunTicketTasks();
ticketTasks.RunBackgroundTasks();
}
Apparently, this starts the task on the same thread (I believe). The task is started and running successfully, but the UI is beeing blocked due to long running operations. Meanwhile, when I uncomment the last line ticketTasks.RunBackgroundTasks(); the UI just runs fine.
How do I start the task in the background so that my User Interface is still responsive?
EDIT:
The reason I started the task this way was because of exception handling.
I can successfully start the task on a different thread as suggested with Task.Run(async () => await ticketTasks.RunBackgroundTasks()); But then I will not receive any exception if something goes wrong.
How can I start the task not blocking the UI and still receive the exception details if an exception is thrown?
The Internet states the following method:
await Task.Run(() => ticketTasks.RunBackgroundTasks());
But this will not work because The await operator can only be used within an async method.
One way to do it is to wrap the task in an async event callback:
public partial class MainWindow : Window
{
// I mocked your missing types, replace with your own
class AssignTickets
{
public Task AssignTicketCreator() => Task.CompletedTask;
}
public class RunTicketTasks
{
public async Task RunBackgroundTasks()
{
AssignTickets assignTickets = new AssignTickets();
int x = 0;
while (true)
{
await assignTickets.AssignTicketCreator();
await Task.Delay(1000);
var isRunningOnDispatcher = Application.Current.Dispatcher ==
System.Windows.Threading.Dispatcher.FromThread(Thread.CurrentThread);
Trace.WriteLine($"One second passed, running on dispatcher: {isRunningOnDispatcher}");
// exception will also get thrown on the main thread
if (x++ > 3) throw new Exception("Hello!");
}
}
}
public MainWindow()
{
InitializeComponent();
// event fires on UI thread!
this.Loaded += async (sender, args) =>
{
try
{
await new RunTicketTasks().RunBackgroundTasks();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
};
}
}

how to stop async method execution in c#?

I am using an asynchronous method. How can I stop its execution when a Timer raises a timeout event?
My code:
public async Task<object> Method()
{
cts = new CancellationTokenSource();
try
{
timer = new System.Timers.Timer(3000);
timer.Start();
timer.Elapsed += (sender, e) =>
{
try
{
timer_Elapsed(sender, e, cts.Token, thread);
}
catch (OperationCanceledException)
{
return;
}
catch (Exception ex)
{
return;
}
};
await methodAsync(cts.Token);
return "message";
}
catch (OperationCanceledException)
{
return "cancelled";
}
catch (Exception ex)
{
return ex.Message;
}
}
// Async Call
public async Task<object> methodAsync(CancellationToken ct)
{
try
{
pdfDocument = htmlConverter.Convert("path", "");
}
catch(Exception ex)
{
return x.Message;
}
}
// Timer event
void timer_Elapsed(object sender, ElapsedEventArgs e, CancellationToken ct)
{
cts.Cancel();
ct.ThrowIfCancellationRequested();
}
Here's how canceling a task works:
public async Task<object> Method()
{
cts = new CancellationTokenSource();
await methodAsync(cts.Token);
return "message";
}
public Task<object> methodAsync(CancellationToken ct)
{
for (var i = 0; i < 1000000; i++)
{
if (ct.IsCancellationRequested)
{
break;
}
//Do a small part of the overall task based on `i`
}
return result;
}
You have to respond to the change in the property of ct.IsCancellationRequested to know when to cancel the task. There is no safe way for one thread/task to cancel another thread/task.
In your case it appears that you are trying to call a single method that doesn't know about the CancellationToken so you can not cancel this task safely. You must let the thread/task continue to completion.
I think you can try mentioning when to cancel it. Something like
cts.CancelAfter(TimeSpan.FromMilliseconds(5000));
Also, you need to use the cancellation token in the called methods. That's when you will know when to cancel.

CancellationTokenSource.Cancel doesn't work during WPF app exit

Here is a simple async call with cancellation code snippet. The code sits in a WPF application class. If I call the Cancel method via a WPF UI Command, the async method will exit properly. However, if the Cancel is invoked during the OnExit method, nothing happens. My actual code requires OnExit call because the async method uses IO resources that should be cleaned up properly.
Any ideas?
Edit: the expected behavior is that Task.Delay method should throw the OperationCancelledException when cancel is invoked. What I want to know is why it doesn't during app exit and if there are work around to get it behaving properly.
public partial class App : Application {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
ListenAsync(source.Token);
}
ManualResetEvent waitHandle = new ManualResetEvent(false);
CancellationTokenSource source = new CancellationTokenSource();
public void Cancel() {
source.Cancel();
}
async void ListenAsync(CancellationToken token) {
try {
while (true) {
await Task.Delay(300000, token);
}
} catch (OperationCanceledException) {
Console.WriteLine("Cancelled");
} catch (Exception err) {
Console.WriteLine(err.Message);
} finally {
Console.WriteLine("Terminate");
waitHandle.Set();
}
}
protected override void OnExit(ExitEventArgs e) {
Cancel();
waitHandle.WaitOne();
base.OnExit(e);
}
}
Found the problem.
The Cancel call during WPF App Exit is on the same synchronization context as the ListenAsync function. Because the thread is blocked by the waitHandle.WaitOne, there is no way for the ListenAsync method to resume executing on the same synchronization context.
The problem can be resolved by changing the async call to
await Task.Delay(300000, token).ConfigureAwait(false);
This allows the remainder of the ListenAsync function to stay on the sync context of the Task.Delay function.
Not sure if this would solve the issue at hand but per your posted code context , Well probably you can use IsCancellationRequested property of the token object and check that property for the cancellation request and break out of your listen loop like
async void ListenAsync(CancellationToken token) {
try {
while (true)
{
if(token.IsCancellationRequested)
break;
await Task.Delay(300000, token);
}
}

Cancelling execution of a method called with await

I'm trying to cancel the execution of the DoSomethingAsync method which I call using await.
When I click on the cancel button the execution is not cancelled and I don't see the "Execution was cancelled" message box, but instead I see the other message box.
I don't understand why it's not working. I am still learning this part of C# and I took this example at http://www.codeproject.com/Articles/127291/C-vNext-New-Asynchronous-Pattern#heading0015 (I simplified it).
public class MyClass : Class
{
CancellationTokenSource cts;
private async void searchButton_Click(object sender, EventArgs e)
{
await DoSomethingAsync();
}
private void cancelButton_Click(object sender, EventArgs e)
{
cts.Cancel();
}
async void DoSomethingAsync()
{
cts = new CancellationTokenSource();
try
{
await SuperSlowProcess();
MessageBox.Show("You will only see this if execution is not cancelled");
}
catch (TaskCanceledException)
{
MessageBox.Show("Execution was cancelled");
}
}
}
In order to make it working, you actually need to use CancellationToken in the SuperSlowProcess:
public Task SuperSlowProcess(CancellationToken cancellationToken)
{
return Task.Run(() => {
// you need to check cancellationToken periodically to check if cancellation has been requested
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested(); // this will throw OperationCancelledException after CancellationTokenSource.Cancel() is called
Thread.Sleep(200); // to emulate super slow process
}
});
}
Of course, it depends on the implementation of SuperSlowProcess. If it's not possible to check CancellationToken periodically, you could check it only once - in the very end, something like that:
public async Task SuperSlowProcess2(CancellationToken cancellationToken)
{
var response = await CallExternalApi();
cancellationToken.ThrowIfCancellationRequested();
}
and then
async void DoSomethingAsync()
{
cts = new CancellationTokenSource();
try
{
await SuperSlowProcess(cts.Token);
MessageBox.Show("You will only see this if execution is not cancelled");
}
catch (OperationCanceledException) // Note that exception type is different
{
MessageBox.Show("Execution was cancelled");
}
}

Asynchronous methods and a event

I use awesomium to automate a site. I tried to use asynchronous programming, because I didn't want my GUI to freeze, but I have a problem at a event (a popup appear and I want to make some actions in this popup until I close it) when the application doesn't continue how I want. After the event is fired I want my application to continue with the event method (webc_ShowCreatedWebView and after with popupTwitter(method), but I find that when executing JavaScript code, the control returns in the While from the first method. How could I do that after the earnpoints method is called and the events is fired to finish the event and the method and after that the control return in the while.
private async void button4_Click(object sender, EventArgs e)
{
Twitter twitter = new Twitter(webView);
twitter.Login(webView);
webView.ShowCreatedWebView += webc_ShowCreatedWebView;
addmefast.Login(webView);
int i = 0;
while (i < 10)
{
Task earnpoints = EarnPoints(webView);
await earnpoints;
//Here i don't want to continue until EarnPoints method > webc_ShowCreatedWebView event > popupTwitter method it's finished.
i++;
}
}
public async Task EarnPoints(IWebView web)
{
web.Source = "http://addmefast.com/free_points/twitter".ToUri();
await Task.Delay(3000);
web.ExecuteJavascript("document.getElementsByClassName('single_like_button btn3-wrap')[0].click();"); //event fired: webc_ShowCreatedWebView
}
async void webc_ShowCreatedWebView(object sender, ShowCreatedWebViewEventArgs e)
{
WebView view = new WebView(e.NewViewInstance);
await popupTwitter(view);
}
async Task popupTwitter(WebView view)
{
Popupform FormTwitter = new Popupform(view);
FormTwitter.Show();
await Task.Delay(6000);
FormTwitter.Twitter();
await Task.Delay(2000);
FormTwitter.Close();
await Task.Delay(4000);
}
I also had problems implementing async methods with awesomium, but got it working.
First I made this wrapper. Have to be created on the main thread.
public class AsyncWebView
{
public static SynchronizationContext _synchronizationContext;
private readonly WebView _webView;
public AsyncWebView()
{
_synchronizationContext = SynchronizationContext.Current;
_webView = WebCore.CreateWebView(1024, 900);
}
public async Task Navigate(String url)
{
Debug.WriteLine("Navigating");
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
FrameEventHandler handler = (sender, args) =>
{
Debug.WriteLine(args.Url);
if (!_webView.IsNavigating && !_webView.IsLoading)
tcs.SetResult(true);
};
_webView.LoadingFrameComplete += handler;
_synchronizationContext.Send(SetWebViewSource, url);
await tcs.Task;
_webView.LoadingFrameComplete -= handler;
Debug.WriteLine("Done");
}
private void SetWebViewSource(object url)
{
_webView.Source = new Uri((string)url);
}
}
Usage:
async Task test()
{
await webView.Navigate("http://www.nytimes.com");
Debug.WriteLine("All done");
}
Just make sure you have a SynchronizationContext where the AsyncWebView constructor is called from.

Categories

Resources