C# 创建具有心跳的任务

C# 创建具有心跳的任务,c#,.net,task-parallel-library,async-await,azureservicebus,C#,.net,Task Parallel Library,Async Await,Azureservicebus,我希望运行一个具有“”的,在任务完成之前以特定时间间隔保持运行的 我认为这样的扩展方法会很有效: public static async Task WithHeartbeat(this Task primaryTask, TimeSpan heartbeatInterval, Action<CancellationToken> heartbeatAction, CancellationToken cancellationToken) 此程序应输出: Starting long ta

我希望运行一个具有“”的,在任务完成之前以特定时间间隔保持运行的

我认为这样的扩展方法会很有效:

public static async Task WithHeartbeat(this Task primaryTask, TimeSpan heartbeatInterval, Action<CancellationToken> heartbeatAction, CancellationToken cancellationToken)
此程序应输出:

Starting long task
Heartbeat 1
Heartbeat 2
Heartbeat 3
Heartbeat 4
Heartbeat 5
Heartbeat 6
Heartbeat 7
Heartbeat 8
Heartbeat 9
Long running task completed!
请注意,它不应(在正常情况下)输出“心跳10”,因为心跳在初始超时(即1秒)后开始。类似地,如果任务花费的时间少于心跳间隔,那么心跳根本不应该发生

实现这一点的好方法是什么

背景信息:我有一个服务正在侦听Azure队列。在完成处理之前,我不希望看到该消息(这将永久性地将其从队列中删除),这可能需要比最大消息5分钟更长的时间。因此,我需要使用此心跳方法在锁定持续时间到期之前调用,以便在长时间处理过程中消息不会超时。

以下是我的尝试:

public static class TaskExtensions {
    /// <summary>
    /// Issues the <paramref name="heartbeatAction"/> once every <paramref name="heartbeatInterval"/> while <paramref name="primaryTask"/> is running.
    /// </summary>
    public static async Task WithHeartbeat(this Task primaryTask, TimeSpan heartbeatInterval, Action<CancellationToken> heartbeatAction, CancellationToken cancellationToken) {
        if (cancellationToken.IsCancellationRequested) {
            return;
        }

        var stopHeartbeatSource = new CancellationTokenSource();
        cancellationToken.Register(stopHeartbeatSource.Cancel);

        await Task.WhenAny(primaryTask, PerformHeartbeats(heartbeatInterval, heartbeatAction, stopHeartbeatSource.Token));
        stopHeartbeatSource.Cancel();
    }
        
    private static async Task PerformHeartbeats(TimeSpan interval, Action<CancellationToken> heartbeatAction, CancellationToken cancellationToken) {
        while (!cancellationToken.IsCancellationRequested) {
            try {
                await Task.Delay(interval, cancellationToken);
                if (!cancellationToken.IsCancellationRequested) {
                    heartbeatAction(cancellationToken);
                }
            }
            catch (TaskCanceledException tce) {
                if (tce.CancellationToken == cancellationToken) {
                    // Totally expected
                    break;
                }
                throw;
            }
        }
    }
}
PerformHeartbeat可以替换为异步调用,这样您就不必像操作方法那样浪费线程时间使用阻塞调用

我是,但我也愿意用更优雅的方法来解决这个问题。

以下是我的方法

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Start Main");
        StartTest().Wait();
        Console.ReadLine();
        Console.WriteLine("Complete Main");
    }

    static async Task StartTest()
    {
        var cts = new CancellationTokenSource();

        // ***Use ToArray to execute the query and start the download tasks. 
        Task<bool>[] tasks = new Task<bool>[2];
        tasks[0] = LongRunningTask("", 20, cts.Token);
        tasks[1] = Heartbeat("", 1, cts.Token);

        // ***Call WhenAny and then await the result. The task that finishes 
        // first is assigned to firstFinishedTask.
        Task<bool> firstFinishedTask = await Task.WhenAny(tasks);

        Console.WriteLine("first task Finished.");
        // ***Cancel the rest of the downloads. You just want the first one.
        cts.Cancel();

        // ***Await the first completed task and display the results. 
        // Run the program several times to demonstrate that different
        // websites can finish first.
        var isCompleted = await firstFinishedTask;
        Console.WriteLine("isCompleted:  {0}", isCompleted);
    }

    private static async Task<bool> LongRunningTask(string id, int sleep, CancellationToken ct)
    {
        Console.WriteLine("Starting long task");


        await Task.Delay(TimeSpan.FromSeconds(sleep));

        Console.WriteLine("Completed long task");
        return true;
    }

    private static async Task<bool> Heartbeat(string id, int sleep, CancellationToken ct)
    {
        while(!ct.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromSeconds(sleep));
            Console.WriteLine("Heartbeat Task Sleep: {0} Second", sleep);
        }

        return true;
    }

}
使用系统;
使用系统线程;
使用System.Threading.Tasks;
命名空间控制台应用程序3
{
班级计划
{
静态void Main(字符串[]参数)
{
控制台写入线(“启动主”);
StartTest().Wait();
Console.ReadLine();
控制台写入线(“完整主控”);
}
静态异步任务StartTest()
{
var cts=新的CancellationTokenSource();
//***使用ToArray执行查询并启动下载任务。
任务[]任务=新任务[2];
任务[0]=LongRunningTask(“,20,cts.Token);
任务[1]=心跳(“,1,cts.Token);
//***呼叫WhenAny,然后等待结果。完成的任务
//first被分配给firstFinishedTask。
Task firstFinishedTask=等待任务。WhenAny(任务);
Console.WriteLine(“第一个任务完成”);
//***取消其余的下载。您只需要第一个。
cts.Cancel();
//***等待第一个完成的任务并显示结果。
//运行程序数次,以演示不同的
//网站可以先完成。
var isCompleted=等待firstFinishedTask;
WriteLine(“isCompleted:{0}”,isCompleted);
}
私有静态异步任务LongRunningTask(字符串id、int-sleep、CancellationToken-ct)
{
Console.WriteLine(“开始长任务”);
等待任务延迟(TimeSpan.FromSeconds(sleep));
Console.WriteLine(“已完成的长任务”);
返回true;
}
私有静态异步任务检测信号(字符串id、int sleep、CancellationToken ct)
{
而(!ct.iscancellationrequest)
{
等待任务延迟(TimeSpan.FromSeconds(sleep));
WriteLine(“心跳任务睡眠:{0}秒”,睡眠);
}
返回true;
}
}

}

这听起来类似于报告异步任务中的进度(只是触发报告的是一个时间间隔,除了心跳计数之外,没有真正的进度可报告)。这些链接有帮助吗@蒂姆斯。它们很相似,但并不完全符合我的要求,特别是在任务快速完成时从不报告的情况下。此外,心跳不知道每说一句的进度。但是,我很高兴看到您是否可以实现与我的扩展API相匹配的进度方法,并与更简单的代码具有相同的净效果。您好,我是从您对我感兴趣的问题的评论中获得这篇文章的。对于SB队列,您确切地在何时何地更新锁?我的工作者角色本身只是一个线程,消息接收和处理在run()方法的while循环中运行。@Aravind当我收到SB消息时,我创建一个任务来处理它。只要任务正在运行,它就会使用这个heartbeat助手进行心跳。哦,好的。由于我使用的这个消息处理可能不是那么频繁,所以我没有创建用于处理每条消息的任务。我在示例代码中没有找到RenewLock的用法,这就是为什么这么问的原因。这是一个旧的-但是
任务。WhenAny
应该更改为
任务。WhenAll
在“异步”版本中?我正在处理一个类似的问题,在主要任务完成后,我似乎从未停止过心跳。但是现在,当任何一个看起来对我来说都可以正常工作时,再改回
    /// <summary>
    /// Awaits a fresh Task created by the <paramref name="heartbeatTaskFactory"/> once every <paramref name="heartbeatInterval"/> while <paramref name="primaryTask"/> is running.
    /// </summary>
    public static async Task WithHeartbeat(this Task primaryTask, TimeSpan heartbeatInterval, Func<CancellationToken, Task> heartbeatTaskFactory, CancellationToken cancellationToken) {
        if (cancellationToken.IsCancellationRequested) {
            return;
        }

        var stopHeartbeatSource = new CancellationTokenSource();
        cancellationToken.Register(stopHeartbeatSource.Cancel);

        await Task.WhenAll(primaryTask, PerformHeartbeats(heartbeatInterval, heartbeatTaskFactory, stopHeartbeatSource.Token));

        if (!stopHeartbeatSource.IsCancellationRequested) {
            stopHeartbeatSource.Cancel();
        }
    }

    public static Task WithHeartbeat(this Task primaryTask, TimeSpan heartbeatInterval, Func<CancellationToken, Task> heartbeatTaskFactory) {
        return WithHeartbeat(primaryTask, heartbeatInterval, heartbeatTaskFactory, CancellationToken.None);
    }

    private static async Task PerformHeartbeats(TimeSpan interval, Func<CancellationToken, Task> heartbeatTaskFactory, CancellationToken cancellationToken) {
        while (!cancellationToken.IsCancellationRequested) {
            try {
                await Task.Delay(interval, cancellationToken);
                if (!cancellationToken.IsCancellationRequested) {
                    await heartbeatTaskFactory(cancellationToken);
                }
            }
            catch (TaskCanceledException tce) {
                if (tce.CancellationToken == cancellationToken) {
                    // Totally expected
                    break;
                }
                throw;
            }
        }
    }
private static async Task PerformHeartbeat(CancellationToken cancellationToken) {
    Console.WriteLine("Starting heartbeat {0}", ++_heartbeatCount);
    await Task.Delay(1000, cancellationToken);
    Console.WriteLine("Finishing heartbeat {0}", _heartbeatCount);
}
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Start Main");
        StartTest().Wait();
        Console.ReadLine();
        Console.WriteLine("Complete Main");
    }

    static async Task StartTest()
    {
        var cts = new CancellationTokenSource();

        // ***Use ToArray to execute the query and start the download tasks. 
        Task<bool>[] tasks = new Task<bool>[2];
        tasks[0] = LongRunningTask("", 20, cts.Token);
        tasks[1] = Heartbeat("", 1, cts.Token);

        // ***Call WhenAny and then await the result. The task that finishes 
        // first is assigned to firstFinishedTask.
        Task<bool> firstFinishedTask = await Task.WhenAny(tasks);

        Console.WriteLine("first task Finished.");
        // ***Cancel the rest of the downloads. You just want the first one.
        cts.Cancel();

        // ***Await the first completed task and display the results. 
        // Run the program several times to demonstrate that different
        // websites can finish first.
        var isCompleted = await firstFinishedTask;
        Console.WriteLine("isCompleted:  {0}", isCompleted);
    }

    private static async Task<bool> LongRunningTask(string id, int sleep, CancellationToken ct)
    {
        Console.WriteLine("Starting long task");


        await Task.Delay(TimeSpan.FromSeconds(sleep));

        Console.WriteLine("Completed long task");
        return true;
    }

    private static async Task<bool> Heartbeat(string id, int sleep, CancellationToken ct)
    {
        while(!ct.IsCancellationRequested)
        {
            await Task.Delay(TimeSpan.FromSeconds(sleep));
            Console.WriteLine("Heartbeat Task Sleep: {0} Second", sleep);
        }

        return true;
    }

}