在上位机和MES通信,尤其是上位机向MES发送数据的的时候,经常需要设置一个超时处理的时间和重复执行的次数。例如MES的超时响应事件是5秒,超过5秒的话,上位机重新访问MES一次,连续三次都超时的话,把要上传的数据缓存到本地。
关于上位机和MES系统的通讯方式,有的工厂是WebService(数据格式有JSON和XML之分),有的工厂是Socket,有的工厂是自己封装的库,所以写了段儿程序执行任务的代码,通讯方式如何都可以写到TASK中
/// <summary>
/// 执行任务超时的话,取消当前执行的任务,重新执行任务。
/// 在最大重新执行次数内执行完,则执行onSuccess委托
/// 执行次数达到最大执行次数,任务还是超时的话,则执行onFailure委托
/// </summary>
/// <param name="operation">要执行的任务</param>
/// <param name="maxRetryCount">最大重复执行的次数</param>
/// <param name="timeout">单次执行任务超时的时间</param>
/// <param name="onSuccess">在最大重新执行次数内执行完,执行的委托</param>
/// <param name="onFailure">执行次数达到最大执行次数,任务还是超时的话,执行的委托 </param>
/// <returns></returns>
public static async Task ExecuteWithRetriesAsync(
Func<Task> operation,
int maxRetryCount,
TimeSpan timeout,
Func<Task> onSuccess,
Func<Task> onFailure)
{
while (true)
{
if (maxRetryCount == 0)
{
// 超过最大重试次数,执行失败委托并退出函数
await onFailure(); //失败该走的事情,比如把数据缓存到本地
return;
}
var task = operation();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
{
try
{
// 如果成功,执行成功委托并退出函数
await onSuccess();
return;
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"任务超时取消, message: {ex.Message}" + " " + DateTime.Now);
}
}
else
{
// 超时,取消任务
await CancelTaskAsync(task);
}
Console.WriteLine($"任务超时,重试任务, retry count: {maxRetryCount}");
maxRetryCount--;
}
}
/// <summary>
/// 取消任务
/// 在 CancelTaskAsync 方法中,不应该释放 CancellationTokenSource 对象,它的责任只是调用任务并稍后取消它(用于超时)。
/// 在正常情况下,CancellationTokenSource对象将由垃圾回收器在不再使用它时自动释放。因此,在 CancelTaskAsync 中不需要对 cts 对象进行显式释放。
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public static async Task CancelTaskAsync(Task task)
{
var cts = new CancellationTokenSource();
try
{
cts.Cancel();
await task;
}
catch (OperationCanceledException ex)
{
Console.WriteLine($"任务取消CancelTaskAsync, message: {ex.Message}");
}
}
/// <summary>
/// 模拟一个需要很长时间才能执行完毕的任务
/// </summary>
/// <param name="timeOutSec"></param>
/// <returns></returns>
public static async Task LongRunningOperationAsync(int timeOutSec)
{
using (var tokenSource = new CancellationTokenSource())
{
Console.WriteLine("");
// 确保在超时后取消任务
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeOutSec));
// 在任务执行期间定期检查 CancellationToken,
// 假设当前任务要执行8秒
await Task.Delay(TimeSpan.FromSeconds(8), tokenSource.Token);
Console.WriteLine("****Operation succeeded.");
}
}
注意上面这段代码使用了 Task.WhenAny 方法,该方法接受一个 Task 对象数组,并返回一个 Task<Task> 对象,该对象的结果是完成最先完成的 Task 对象。使用 await 运算符等待 Task.WhenAny 方法返回的 Task,这将导致代码在这里等待,直到指定的任务 task 或超时任务 Task.Delay 完成为止。 如果最先完成的任务是指定的任务 task,则 Task.WhenAny 的返回结果即为 task,这意味着 await Task.WhenAny(task, Task.Delay(timeout)) 将返回 task 对象的结果,此时,if 语句的条件判断结果为 True,进入 if 块内部的代码。否则,如果超时任务先完成,Task.WhenAny 的返回结果将是超时任务 Task.Delay,在这种情况下,if 语句将不会执行,代码将继续执行 Task.Delay 后面的语句。 因此,这个 if 语句是用来确定任务是否在规定的超时时间内完成的。如果完成,在 if 块内部执行相应的代码;如果没有,在超时后执行某种其他的操作。
模拟条件执行任务,模拟超时的时间为5秒,最多执行任务3次。
public static async Task DoSomethingAgainAndAgain()
{
// 累计重试和最大重试次数初始化
int maxRetryCount = 3;//最多执行任务的次数
int timeOutSec = 5;//每次执行任务的超时时间
// 成功和失败的委托初始化
Func<Task> onSuccess = () => Task.FromResult<object>("do nothing");
Func<Task> onFailure = () => Task.FromResult<object>("do nothing");
// 执行异步任务并在超时后重试最大重试次数
await ExecuteWithRetriesAsync(
async () => await LongRunningOperationAsync(timeOutSec),
maxRetryCount,
TimeSpan.FromSeconds(timeOutSec),
onSuccess, onFailure);
}
也可以用下面这种方法来模拟任务
static async Task LongRunningOperationAsync2(int timeOutSec)
{
using (var tokenSource = new CancellationTokenSource())
{
var startTime = DateTime.Now;
// 在任务执行期间定期检查 CancellationToken,
// 如果它已取消,可以安全地退出。
while (true)
{
if ((DateTime.Now - startTime) >= TimeSpan.FromSeconds(timeOutSec))
{
tokenSource.Cancel();
}
if (tokenSource.IsCancellationRequested)
{
return;
}
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
编写上述代码用了如下知识点,这些都很重要,希望大家好好学习,天天想上
-
异步编程 -
ExecuteWithRetriesAsync
方法的定义和实现都是基于异步编程模型的。 -
委托和Lambda表达式 - 函数
ExecuteWithRetriesAsync
接受两个参数类型为Func<Task>
的委托参数,在函数体内被直接调用,使用了 Lambda 表达式来定义这些委托。 -
异常处理 - 为了捕获可能在异步执行过程中产生的异常,代码块使用了
try/catch
语句。 -
Task 和 Task.Delay - Task 类型用于表示异步操作的进程,而 Task.Delay 方法用于异步延迟一段时间后执行一个操作。
-
Task.WhenAny - Task.WhenAny 方法用于等待任何一个任务完成,如果第一个任务完成,则返回该任务,如果没有完成,则返回一个默认的任务。在这个方法中,使用 Task.WhenAny 方法来判断当前任务是否已经超时。
-
async/await 关键字 - 使用了
async/await
关键字以帮助简化对异步方法的调用