IsCancellationRequested未被调用

时间:2017-10-24 08:20:19

标签: c# cancellationtokensource

我正在尝试制作一个简单的程序来学习如何使用取消令牌,但它们似乎给了我很多问题。通过调整CancelAfter值,我绝对可以阻止任务死亡,但IsCancellationRequested并没有要求我。由于CancelAfter似乎正在正常执行,因此IsCancellationRequested在100%的时间内失败让我感到非常困惑。

这是代码: (注意,这是一个精简的例子)

初始化任务的功能:

void IPCheck() {
    var cToken = new CancellationTokenSource();
    cToken.CancelAfter(1000);

    string tempIP = "123.123.123.123";

    Task.Factory.StartNew(() => PingTask(tempIP, cToken.Token), cToken.Token);
}

成功(?)取消但未调用IsCancellationRequested的任务。

void PingTask(string addressString, CancellationToken cancelToken) {

    Ping currentPing = new Ping();
    PingReply replyPing;
    replyPing = currentPing.Send(addressString);

    string returnString = "Return String Unmodified." + Environment.NewLine;


    if (replyPing.Status == IPStatus.Success) {
        returnString = "Ping to " + addressString + " successful." + Environment.NewLine);
    }

    else if (replyPing.Status == IPStatus.TimedOut) {
        returnString = "Ping to " + addressString + "timed out." + Environment.NewLine);
    }

    if (cancelToken.IsCancellationRequested) {
        returnString = "Cancellation Requested for " + addressString + Environment.NewLine);
    }

    TaskHelperReturnString(returnString);
    return;
}

除非成功或超时,CancelAfter设置为10000returnString始终写"Return String Unmodified."

成功的ping将立即结束。使用CancelAfter(1000)时,没有足够的时间来超时并且不会打印任何内容,包括TaskHelperReturnString()

我使用的IP总是失败,所以它对测试很有用。

1 个答案:

答案 0 :(得分:0)

在所有受支持的.NET版本(4.5.2+)中,您可以使用async/awaitSendPingAsync异步发送ping,而无需Task.Run。即使没有它们,你也不需要取消源,因为SendAsync(IPAddress,int)接受超时参数。

您的代码可能如下所示:

async Task<string> IPCheck()
{
    Ping currentPing = new Ping();
    var replyPing= await currentPing.SendPingAsync(addressString,1000);        
    switch(replyPint.Status)
    {
        case IPStatus.Success:
            var okString = $"Ping to {addressString} successful.\n";
            SuccessActions(0, arrayIndex);
            return okString;
        case IPStatus.Timeout:
            return  $"Ping to {addressString} timed out.\n";
        default:
            // ????
    }
}

即使你想要ping超时的更大任务超时(为什么?),你可以使用Task.DelayTask.WhenAny而不是取消源。比ping超时短的任务超时没有多大意义,因为你永远不会以这种方式获得ping超时。

使用Task.Delay:

async Task<string> IPCheck()
{
    Ping currentPing = new Ping();

    var pingTask= currentPing.SendPingAsync(addressString,1000);        
    var delayTask=Task.Delay(2000);
    var finished =  await Task.WhenAny(delayTask,pingTask);
    if (finished == delayTask)
    {
        return "Long timeout!"
    }

    var replyPing=await pingTask;

    switch(replyPing.Status)
    {
        case IPStatus.Success:
            var okString = $"Ping to {addressString} successful.\n";
            SuccessActions(0, arrayIndex);
            return okString;
        case IPStatus.Timeout:
            return  $"Ping to {addressString} timed out.\n";
        default:
            // ????
    }
}

<强>更新

原始代码无法正常工作,因为任务永远不会等待。定义取消源的方法立即退出,强制在源上进行垃圾收集。即使将令牌传递给任务,源也不会保留。 令牌是。由于源不再存在,因此无法通知它生成的任何令牌。

要使原始代码与令牌源一起使用,必须等待任务本身,或者取消源应该具有比方法更大的范围,即它应该是一个字段。

为了做一个正确的例子,假设我们在响应之前需要 5 ping 例如:

更新2018-11-06 添加了CancellationTokenSource的正确处置,以避免充斥定时器队列。 Explanation here

static async Task<string> IPCheck(string address)
{
    using (var cts = new CancellationTokenSource(2000))
    {
        var result = await SendPings(address, 5, cts.Token);
        return result;
    }
}


static async Task<string> SendPings(string address, int count, CancellationToken token)
{
    var responses = new List<PingReply>(5);

    Ping currentPing = new Ping();

    for (int i = 0; i < count; i++)
    {
        if (token.IsCancellationRequested)
        {
            return "Cancelled!";
        }

        var replyPing = await currentPing.SendPingAsync(address, 1000);
        switch (replyPing.Status)
        {
            case IPStatus.Success:
                responses.Add(replyPing);
                break;
            case IPStatus.TimedOut:
                responses.Add(replyPing);
                Console.WriteLine($"Ping to {address} timed out.\n");
                break;
            default:
                responses.Add(replyPing);
                break;
        }
    }
    var avgTime = responses.Average(resp => resp.RoundtripTime);
    return $"All pings returned. Average roundtrip {avgTime}";
}

在这种情况下,IPCheck方法不会阻止,但是在所有5次ping都在2秒内完成或超时(ping或整体)发生之前,它都不会完成。

如果ping时间过长而且总超时太短,则IsCancellationRequested变为true并返回Cancelled