[英]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.