Někdy je potřeba provést operaci, která může déle trvat. Navíc v případě, že se tato operace provádí delší než maximálně očekávanou dobu, je pak obvykle nutné provést nějakou další operaci - například prodloužit "zámek" nad vzkazem ve frontě apod. Obvykle se tedy spustí čekací smyčka, která hlídá nepřekročení časového limitu a je zrušena po úspěšném provedení hlavní operace.
Takovou delší operaci lze napodobit následujícím kódem:
var cancellationSource = new CancellationTokenSource();
WatchExeTime(InformAboutLongTime, cancellationSource.Token);
Console.WriteLine("Going to execute a long time operation.");
Thread.Sleep(16000);
cancellationSource.Cancel();
Console.WriteLine("The long time operation executed");
Console.ReadLine();
Samozřejmě, že v reálném kódu bude délka trvání operace proměnná, nastavil jsem zde napevno 16 sekund. Každé tři sekundy je přitom nutné informovat o běhu operace, to zde zastává metoda InformAboutLongTime, která jen na konzoli vypíše hlášení - opět, v reálném kódu by se volala jiná operace, například již zmíněné prodloužení zámku nad zprávou.
static private void InformAboutLongTime()
{
Console.WriteLine("The operation in progress, please wait");
}
Vlastní hlídací metoda pak byla realizována takto:static private void WatchExeTime(Action action, CancellationToken cancellationToken)
{
Task.Run(
() =>
{
var sw = new Stopwatch();
sw.Start();
var invokeAt = TimeSpan.FromSeconds(3);
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
if (sw.Elapsed > invokeAt)
{
action();
sw.Restart();
}
}
},
cancellationToken).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);
}
A co je na něm špatně? Kód ve smyčce se neustále opakuje, CPU je tak zbytečně vytížena. Pokud je tato aplikace, která vlastně nic nedělá, spuštěna, a pomocí Diagnostic Tools sledujete vytížení CPU, může tato aplikace vytížit CPU z nezanedbatelné části - bude se jednat o desítky procent:
private static void WatchExeTime(Action action, CancellationToken cancellationToken)
{
Task.Run(
() =>
{
var waitFor = TimeSpan.FromSeconds(3);
while (cancellationToken.IsCancellationRequested == false)
{
var cancelled = cancellationToken.WaitHandle.WaitOne(waitFor);
if (cancelled)
{
break;
}
action();
}
},
cancellationToken).ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);
}
Mimo to, že je kód kratší a tedy přehlednější a udržovatelnější, klesne hlavně i vytížení CPU - a to velmi podstatně:
Celý kód je dostupný zde a o vhodnosti tohoto přístupu se lze přesvědčit záměnou metod WatchExeTime (nevhodný přístup) a WatchExeTime2 (vhodný přístup) :
Kdyz uz jsi sahnul na WaitHandle, bylo by dobre CancellationTokenSource disposnout (nebo to delat obecne vzdycky). A misto WaitHandle.WaitOne, bych to cele prepsal na Task.Delay(3000) a smycku a je to pak na par radku.
OdpovědětVymazats tim Dispose mas pravdu, diky. S Task.Delay to myslis nejak takto:
OdpovědětVymazatTask.Run(
async () =>
{
while (true)
{
await Task.Delay(3000, cancellationToken);
action();
}
},cancellationToken)
.ContinueWith(t => { Console.WriteLine("Wake up"); },TaskContinuationOptions.OnlyOnCanceled)
.ConfigureAwait(false);
edremit
OdpovědětVymazatardahan
kırıkkale
kırşehir
tunceli
KJPF8