问题
I'm trying to make use of cancellation tokens as described in this FAQ. This was my initial thought:
private async void OnLoginButtonClicked(object sender, EventArgs e)
{
if (this.cancelToken == null)
{
this.cancelToken = new CancellationTokenSource();
}
try
{
bool loginSuccess = await AsyncLoginTask(this.cancelToken.Token);
if (loginSuccess)
{
// Show main page
}
}
catch (OperationCanceledException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
finally
{
this.cancelToken = null;
}
}
private async Task<bool> AsyncLoginTask(CancellationToken cancellationToken = default(CancellationToken))
{
// Pass the token to HttpClient()
}
Now I adapted it and this is the result:
private async void OnLoginButtonClicked(object sender, EventArgs e)
{
this.cancelToken?.Dispose();
this.cancelToken = new CancellationTokenSource();
try
{
var ui = TaskScheduler.FromCurrentSynchronizationContext();
var loginTask = Task.Factory.StartNew(async () =>
{
bool loginSuccess = await AsyncLoginTask(this.cancelToken.Token);
}, this.cancelToken.Token);
var displayResults = loginTask.ContinueWith(resultTask =>
{
// How do I know if the login was successful?
// Because AsyncLoginTask() returns bool.
System.Diagnostics.Debug.WriteLine("done");
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
ui);
var displayCancelledTasks = loginTask.ContinueWith(resultTask =>
{
System.Diagnostics.Debug.WriteLine("canceled");
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnCanceled, ui);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
Questions:
- How do I know if the login was successful? Because
AsyncLoginTask()
returnsbool
. - How to create and destroy the token correctly to allow a operation to be started and cancelled multiple times?
- How to handle the task in the task? "done" is shown in the console, while the task (AsyncLoginTask) hasn't finished yet.
回答1:
I'm trying to make use of cancellation tokens as described in this FAQ.
That blog post is using Dynamic Task Parallelism (StartNew
and ContinueWith
). Dynamic Task Parallelism is when you have a lot of CPU-bound operations to do and you don't know how many you have until you're already processing them (i.e., each one you process can add zero or more additional tasks to the same process).
In your case, you have a single asynchronous operation. As such, the approach in that article is completely wrong for your use case. Your original thought was much more correct.
You'd want to do it more like this:
private async void OnLoginButtonClicked(object sender, EventArgs e)
{
// Cancel the previous attempt (if any) and start a new one.
this.cts?.Cancel();
this.cts = new CancellationTokenSource();
try
{
bool loginSuccess = await AsyncLoginTask(this.cts.Token);
// Resolve race condition where user cancels just as it completed.
this.cts.Token.ThrowIfCancellationRequested();
if (loginSuccess)
{
// Show main page
}
}
catch (OperationCanceledException ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
}
private async Task<bool> AsyncLoginTask(CancellationToken cancellationToken = default(CancellationToken))
{
// Pass the token to HttpClient()
}
来源:https://stackoverflow.com/questions/44260010/task-cancellation-with-async-task