Reputation:
I'm trying to capture the state of a long running task during the Form1_FormClosing
event.
The long running task comprises of async/await calls using the HttpClient.
When I start the task running it runs in loop until cancelled. However, when I close the form with the task still running the task status is not as expected and showing status RanToCompletion
and IsCompleted==true
I've replicated the problem using method RunLongRunningMethodTest
Task.Delay(2000);
works okay but await Task.Delay(2000);
replicates the same issue. I'm not sure how to get around the fact that GetUrl
must also be async due to the GetAsync method.
How can I fix the code below so that Form1_FormClosing correctly reports that the task is still running if closed while it runs? I want to check the task state and cancel if still running and wait for the cancel to complete.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private static HttpClient client { get; set; }
private Task task { get; set; }
private CancellationTokenSource cts { get; set; }
private bool buttonStartStopState { get; set; }
public Form1()
{
InitializeComponent();
}
private async void RunLongRunningMethodTest(CancellationToken cancellationToken)
{
try
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
Debug.WriteLine(DateTime.Now);
Task.Delay(2000); // task.IsCompleted = false - Works okay
//await Task.Delay(2000); // task.IsCompleted = true - Not correct
}
}
catch (OperationCanceledException)
{
// Just exit without logging. Operation cancelled by user.
}
catch (Exception ex)
{
// Report Error
}
}
private async void RunLongRunningMethod(CancellationToken cancellationToken)
{
try
{
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
var success = await GetUrl("https://www.bbc.co.uk/");
Thread.Sleep(2000);
}
}
catch (OperationCanceledException)
{
// Just exit without logging. Operation cancelled by user.
}
catch (Exception ex)
{
// Report Error
}
}
private async Task<bool> GetUrl(string url)
{
if (client == null)
{
client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip });
client.BaseAddress = new Uri("https://www.bbc.co.uk/");
client.DefaultRequestHeaders.Add("Accept", "*/*");
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/62.0");
client.DefaultRequestHeaders.Add("Host", "https://www.bbc.co.uk/");
client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
client.DefaultRequestHeaders.Add("DNT", "1");
}
var response = await client.GetAsync(url);
var contents = await response.Content.ReadAsStringAsync();
return true;
}
private void buttonStartStop_Click(object sender, EventArgs e)
{
buttonStartStopState = !buttonStartStopState;
if(buttonStartStopState)
{
cts = new CancellationTokenSource();
task = new Task(() => RunLongRunningMethod(cts.Token));
task.Start();
}
else
{
cts.Cancel();
cts = null;
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (task != null)
{
var taskStatus = $"{task.Status} {task.IsCompleted}";
}
}
}
}
Upvotes: 1
Views: 6974
Reputation: 32068
The first problem is your use of async void
in your long running methods. When the code reaches the await
line, the control is yielded back to the parent, and since the Task does not have anything else to run, it correctly reports that it has finished.
So, let's fix that:
private async Task RunLongRunningMethodTest
private async Task RunLongRunningMethod
The second problem is the use of the Task
constructor. Not only its use is discouraged, but it is also unnecessary in your code. You should be doing just this:
if (buttonStartStopState)
{
cts = new CancellationTokenSource();
task = RunLongRunningMethod(cts.Token); // will only compile after fixing the first problem
}
As an additional note, notice that using Task.Delay(2000);
is effectively useless, since you are creating a Task that is thrown away.
Upvotes: 6