JohnWick
JohnWick

Reputation: 5149

C# A task was cancelled

I wrote a static class which tests the internet connectivity of the user, and raises an event if their connection status changes:

class InternetConnectionMonitor
{

    public static EventHandler<EventArgs<bool>> StatusChanged;

    private static bool isCancelled;
    private static bool isConnected;

    private static bool IsConnected
    {
        get
        {
            return isConnected;
        }
        set
        {
            if (isConnected != value)
            {
                StatusChanged(null, new EventArgs<bool>(value));
            }
            isConnected = value;
        }
    }

    public static async void Start(TimeSpan interval)
    {
        //TODO Use a 1st party webpage for connectivity testing.
        using (var client = new HttpClient())
        {
            client.Timeout = TimeSpan.FromSeconds(2);
            while (!isCancelled)
            {
                try
                {
                    await client.GetAsync("http://example.com");
                    IsConnected = true;
                }
                catch (Exception)
                {
                    IsConnected = false;
                }
                await Task.Delay(interval);
            }
        }
    }

    public static void Stop()
    {
        isCancelled = true;
    }
}

This class works wonderfully, however, when doing some other processing using the TPL Dataflow in my app, an exception is raised in the while loop of the Start() method saying the task was cancelled. The reason I am posting here, is because I never cancel any tasks.

Here is the processing I am doing. The task cancelled exception is raised in InternetConnectionMonitor after QueueTests is completed, although QueueTests makes no reference to InternetConnectionMonitor whatsoever.

If I don't call validateProxies(), the exception is never raised.

private async void validateProxies(IEnumerable<Proxy> proxies)
{
    validateProxiesButton.Enabled = false;
    cancelValidatingProxiesButton.Enabled = true;
    addProxiesButton.Enabled = false;
    removeProxiesButton.Enabled = false;
    proxyTester = new ProxyTester();
    await proxyTester.QueueTests(proxies, judges);
    validateProxiesButton.Enabled = true;
    cancelValidatingProxiesButton.Enabled = false;
    addProxiesButton.Enabled = true;
    removeProxiesButton.Enabled = true;
    MessageBox.Show("Complete!");
}

public class ProxyTester
{

    private PauseOrCancelTokenSource pcts = new PauseOrCancelTokenSource();

    public async Task QueueTests(IEnumerable<Proxy> proxies, IEnumerable<ProxyJudge> judges, int maxConcurrency = 100)
    {
        var testProxies = new ActionBlock<(Proxy proxy, IProxyTest test)>((tup) =>
        {
            tup.proxy.Status = ProxyStatus.Testing;
            tup.proxy.Status = tup.proxy.TestValidity(tup.test);
        }, new ExecutionDataflowBlockOptions { CancellationToken = pcts.Token.CancellationToken, MaxDegreeOfParallelism = maxConcurrency });

        //Set each proxies status to Queued, and post to the dataflow block.
        foreach (var proxy in proxies)
        {
            proxy.Status = ProxyStatus.Queued;
            await testProxies.SendAsync((proxy, judges.GetRandomItem()));
        }

        testProxies.Complete();

        try
        {
            await testProxies.Completion;
        }
        catch (Exception)
        {

        }
    }

    public void Cancel()
    {
        pcts.Cancel();
    }

}

Starting InternetConnectionMonitor (requested by JleruOHeP in comments)

public proxyTesterView()
{
    InitializeComponent();

    InternetConnectionMonitor.StatusChanged += InternetConnectionMonitor_StatusChanged;
    InternetConnectionMonitor.Start(TimeSpan.FromSeconds(1));
}

private void InternetConnectionMonitor_StatusChanged(object sender, EventArgs<bool> e)
{
    if (e.Value == true)
    {
        MessageBox.Show("Online");
    }
    else
    {
        MessageBox.Show("Offline");
    }
}

Upvotes: 2

Views: 4167

Answers (1)

JohnWick
JohnWick

Reputation: 5149

Solved my own question and wanted to share my solution. After some thinking, I had a feeling perhaps threadpool threads were becoming exhausted when calling QueueTests, due to the high default max degree of parallelism (100). This threadpool exhaustion seemed to have unintended side effects on the call to client.GetAsync in the Start() method in InternetConnectionMonitor, causing the timeout on the request to improperly trigger, thus causing the TaskCancelledException.

So I spawned my own explicit thread and am synchronously performing the testing within it. Exception gone and working as intended now.

public static void Start()
{
    //TODO Use a 1st party webpage for connectivity testing.
    var t = new Thread(() =>
    {
        while (!isCancelled)
        {
            try
            {
                var req = HttpWebRequest.Create("http://example.com");
                req.Timeout = 1000;
                using (var resp = req.GetResponse())
                {
                    resp.Close();
                }
                IsConnected = true;
            }
            catch (Exception ex)
            {
                IsConnected = false;
            }
            Console.WriteLine(IsConnected);
            Thread.Sleep(1000);
        }
    });
    t.Start();
}

Upvotes: 2

Related Questions