[英]How to limit the actual EXECUTION time of a method
虽然有很多示例展示了如何使用 RX 对方法施加超时,但超时是调用者等待方法完成的时间。 当系统上有很多任务时,该方法甚至可能在开始运行之前就收到超时异常。
这是一个示例程序:
using System;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncTasksStuff
{
internal class Program
{
private static void Main(string[] args)
{
Program program = new Program();
sw.Start();
int numOfTasks = 10;
for (int i = 0; i < numOfTasks; i++)
{
program.SyncMethodWrapper(i, 1000);
}
Console.WriteLine("End program");
Console.ReadKey();
}
private static Stopwatch sw = new Stopwatch();
private void SyncMethod(int millis, int id)
{
//Console.WriteLine(id + " start");
Thread.Sleep(millis);
//Console.WriteLine(id + " end");
}
private async void SyncMethodWrapper(int id, int millis)
{
Console.WriteLine($"{sw.Elapsed}: Start task {id}");
bool done = false;
var task = Task.Run(() =>
{
SyncMethod(millis, id);
done = true;
}
);
try
{
await task.ToObservable().Timeout(TimeSpan.FromMilliseconds(millis+100)).ToTask().ConfigureAwait(false);
}
catch (Exception)
{
}
Console.WriteLine($"{sw.Elapsed}: ID = {id} done = {done}");
}
}
}
这是一个示例输出:
00:00:00.0143282:开始任务0
00:00:00.1256133:开始任务 1
00:00:00.1257378:开始任务 2
00:00:00.1260298:开始任务 3
00:00:00.1261045:开始任务 4
00:00:00.1261440:开始任务 5
00:00:00.1261740:开始任务 6
00:00:00.1262064:开始任务 7
00:00:00.1262301:开始任务8
00:00:00.1262523:开始任务 9
结束程序
00:00:01.0564493:ID = 0 完成 = 真
00:00:01.1264120:ID = 3 完成 = 真
00:00:01.1264120:ID = 1 完成 = 真
00:00:01.1264120:ID = 2 完成 = 真
00:00:02.0474572:ID = 4 完成 = 真
00:00:02.0580636:ID = 9 完成 = 假
00:00:02.0588118: ID = 8 完成 = 假
00:00:02.0591877:ID = 7 完成 = 假
00:00:02.0594568:ID = 6 完成 = 假
00:00:02.0597286:ID = 5 完成 = 假
因此,虽然所有调用的预期输出都是“done = true”,但其中一些调用会收到超时。 如果我理解正确的话,这是因为超时是从调用者开始等待的时间开始测量的(这很有意义,因为通常这是我们想要限制的)。
但是,我想限制该方法的实际执行时间(自调度程序实际开始执行任务以来的时间)。
限制方法实际执行时间的正确方法是什么(不重写同步方法)?
您的代码所做的是有效地将任务的执行排队,并在所有任务的开头设置.Timeout(TimeSpan.FromMilliseconds(millis + 100))
。 所以当任务累积每个Thread.Sleep(millis);
调用,总时间最终超过超时,你开始得到"done = False"
。
但是您真正想做的是“限制方法的实际执行时间”。 这不会发生。 有两种方法可以限制方法的执行时间。 (1) Thread.Abort()
糟糕! 糟糕! 糟糕! 永远不要这样做。 因为它会破坏 .NET 框架的运行时状态。 只有当您试图从应用程序中崩溃时才可以。 (2) 将取消令牌(或等价物)传递给该方法,并让该方法监视取消请求。
现在,至于 Rx .Timeout
方法。 拿这个例子:
Observable
.Start(() => SomeMethodThatTakesBetween1And3Seconds())
.Timeout(TimeSpan.FromSeconds(2.0))
.Subscribe(
x => Console.WriteLine(x),
ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("Done."));
对SomeMethodThatTakesBetween1And3Seconds()
的调用在进行订阅时开始,并且无论超时如何,该方法都会运行到完成。
如果该方法在超时之前返回,则返回值x
。 如果超时,则异常将作为错误返回 - 但SomeMethodThatTakesBetween1And3Seconds()
方法继续运行直至完成。
现在,作为旁注,您不必将 observable 转换为等待它的任务。 Observables 已经是可等待的。 您可以调用await task.ToObservable().Timeout(TimeSpan.FromMilliseconds(millis+100));
.
以下是一些可能对您有所帮助的代码:
var random = new Random();
var query =
from i in Observable.Range(0, 100)
from s in Observable.Start(() =>
{
Thread.Sleep(random.Next(0, 1000));
return i;
}).Timeout(TimeSpan.FromSeconds(0.25), Observable.Return(-1))
where s != -1
select s;
我发现在方法实际启动后的一段时间后强制从方法返回的方法是一种蛮力方法:
private async void SyncMethodWrapper(int id, int millis)
{
Console.WriteLine($"Start task {id}");
SemaphoreSlim ss = new SemaphoreSlim(0);
bool done = false;
System.Timers.Timer timer = null;
Stopwatch sw = null;
var task = Task.Run(() =>
{
timer = new System.Timers.Timer {Interval = millis + 100};
sw = new Stopwatch();
timer.Elapsed += (sender, args) =>
{
try
{
ss.Release(1);
Console.WriteLine($"Timeout {id}");
}
catch (Exception)
{
}
};
timer.Start();
sw.Start();
Console.WriteLine($"start timer {id}");
SyncMethod(millis, id);
done = true;
ss.Release(1);
//Console.WriteLine("done");
}
);
await ss.WaitAsync().ConfigureAwait(false);
Console.WriteLine($"ID = {id} done = {done} elapsed = {sw.Elapsed}");
ss.Dispose();
timer.Dispose();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.