Saya baru mengenal .Net 4.0's Tasks dan saya tidak dapat menemukan apa yang saya pikir akan menjadi pengganti berbasis Tugas atau implementasi Timer, misalnya Tugas periodik. Apa ada yang seperti itu?
Pembaruan Saya datang dengan apa yang menurut saya merupakan solusi untuk kebutuhan saya yaitu membungkus fungsionalitas "Timer" di dalam Tugas dengan Tugas anak semua memanfaatkan CancellationToken dan mengembalikan Tugas untuk dapat berpartisipasi dalam langkah Tugas lebih lanjut.
public static Task StartPeriodicTask(Action action, int intervalInMilliseconds, int delayInMilliseconds, CancellationToken cancelToken)
{
Action wrapperAction = () =>
{
if (cancelToken.IsCancellationRequested) { return; }
action();
};
Action mainAction = () =>
{
TaskCreationOptions attachedToParent = TaskCreationOptions.AttachedToParent;
if (cancelToken.IsCancellationRequested) { return; }
if (delayInMilliseconds > 0)
Thread.Sleep(delayInMilliseconds);
while (true)
{
if (cancelToken.IsCancellationRequested) { break; }
Task.Factory.StartNew(wrapperAction, cancelToken, attachedToParent, TaskScheduler.Current);
if (cancelToken.IsCancellationRequested || intervalInMilliseconds == Timeout.Infinite) { break; }
Thread.Sleep(intervalInMilliseconds);
}
};
return Task.Factory.StartNew(mainAction, cancelToken);
}
Jawaban:
Itu tergantung pada 4,5, tetapi ini berhasil.
public class PeriodicTask { public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken) { while(!cancellationToken.IsCancellationRequested) { await Task.Delay(period, cancellationToken); if (!cancellationToken.IsCancellationRequested) action(); } } public static Task Run(Action action, TimeSpan period) { return Run(action, period, CancellationToken.None); } }
Jelas Anda bisa menambahkan versi generik yang juga membutuhkan argumen. Ini sebenarnya mirip dengan pendekatan lain yang disarankan karena di bawah tenda Task. Penundaan menggunakan pengatur waktu kedaluwarsa sebagai sumber penyelesaian tugas.
sumber
action()
dengan pengulangan!cancelToken.IsCancellationRequested
. Itu lebih baik, bukan?action
eksekusi" benar kan?UPDATE Saya menandai jawaban di bawah ini sebagai "jawaban" karena ini sudah cukup tua sekarang karena kita harus menggunakan pola async / await. Tidak perlu lagi meremehkan ini. LOL
Seperti yang dijawab Amy, belum ada implementasi periodik / timer berbasis Tugas. Namun, berdasarkan UPDATE asli saya, kami telah mengembangkan ini menjadi sesuatu yang sangat berguna dan produksi telah diuji. Pikir saya akan berbagi:
using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication7 { class Program { static void Main(string[] args) { Task perdiodicTask = PeriodicTaskFactory.Start(() => { Console.WriteLine(DateTime.Now); }, intervalInMilliseconds: 2000, // fire every two seconds... maxIterations: 10); // for a total of 10 iterations... perdiodicTask.ContinueWith(_ => { Console.WriteLine("Finished!"); }).Wait(); } } /// <summary> /// Factory class to create a periodic Task to simulate a <see cref="System.Threading.Timer"/> using <see cref="Task">Tasks.</see> /// </summary> public static class PeriodicTaskFactory { /// <summary> /// Starts the periodic task. /// </summary> /// <param name="action">The action.</param> /// <param name="intervalInMilliseconds">The interval in milliseconds.</param> /// <param name="delayInMilliseconds">The delay in milliseconds, i.e. how long it waits to kick off the timer.</param> /// <param name="duration">The duration. /// <example>If the duration is set to 10 seconds, the maximum time this task is allowed to run is 10 seconds.</example></param> /// <param name="maxIterations">The max iterations.</param> /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task /// is included in the total duration of the Task.</param> /// <param name="cancelToken">The cancel token.</param> /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create the task for executing the <see cref="Action"/>.</param> /// <returns>A <see cref="Task"/></returns> /// <remarks> /// Exceptions that occur in the <paramref name="action"/> need to be handled in the action itself. These exceptions will not be /// bubbled up to the periodic task. /// </remarks> public static Task Start(Action action, int intervalInMilliseconds = Timeout.Infinite, int delayInMilliseconds = 0, int duration = Timeout.Infinite, int maxIterations = -1, bool synchronous = false, CancellationToken cancelToken = new CancellationToken(), TaskCreationOptions periodicTaskCreationOptions = TaskCreationOptions.None) { Stopwatch stopWatch = new Stopwatch(); Action wrapperAction = () => { CheckIfCancelled(cancelToken); action(); }; Action mainAction = () => { MainPeriodicTaskAction(intervalInMilliseconds, delayInMilliseconds, duration, maxIterations, cancelToken, stopWatch, synchronous, wrapperAction, periodicTaskCreationOptions); }; return Task.Factory.StartNew(mainAction, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } /// <summary> /// Mains the periodic task action. /// </summary> /// <param name="intervalInMilliseconds">The interval in milliseconds.</param> /// <param name="delayInMilliseconds">The delay in milliseconds.</param> /// <param name="duration">The duration.</param> /// <param name="maxIterations">The max iterations.</param> /// <param name="cancelToken">The cancel token.</param> /// <param name="stopWatch">The stop watch.</param> /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task /// is included in the total duration of the Task.</param> /// <param name="wrapperAction">The wrapper action.</param> /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create a sub task for executing the <see cref="Action"/>.</param> private static void MainPeriodicTaskAction(int intervalInMilliseconds, int delayInMilliseconds, int duration, int maxIterations, CancellationToken cancelToken, Stopwatch stopWatch, bool synchronous, Action wrapperAction, TaskCreationOptions periodicTaskCreationOptions) { TaskCreationOptions subTaskCreationOptions = TaskCreationOptions.AttachedToParent | periodicTaskCreationOptions; CheckIfCancelled(cancelToken); if (delayInMilliseconds > 0) { Thread.Sleep(delayInMilliseconds); } if (maxIterations == 0) { return; } int iteration = 0; //////////////////////////////////////////////////////////////////////////// // using a ManualResetEventSlim as it is more efficient in small intervals. // In the case where longer intervals are used, it will automatically use // a standard WaitHandle.... // see http://msdn.microsoft.com/en-us/library/vstudio/5hbefs30(v=vs.100).aspx using (ManualResetEventSlim periodResetEvent = new ManualResetEventSlim(false)) { //////////////////////////////////////////////////////////// // Main periodic logic. Basically loop through this block // executing the action while (true) { CheckIfCancelled(cancelToken); Task subTask = Task.Factory.StartNew(wrapperAction, cancelToken, subTaskCreationOptions, TaskScheduler.Current); if (synchronous) { stopWatch.Start(); try { subTask.Wait(cancelToken); } catch { /* do not let an errant subtask to kill the periodic task...*/ } stopWatch.Stop(); } // use the same Timeout setting as the System.Threading.Timer, infinite timeout will execute only one iteration. if (intervalInMilliseconds == Timeout.Infinite) { break; } iteration++; if (maxIterations > 0 && iteration >= maxIterations) { break; } try { stopWatch.Start(); periodResetEvent.Wait(intervalInMilliseconds, cancelToken); stopWatch.Stop(); } finally { periodResetEvent.Reset(); } CheckIfCancelled(cancelToken); if (duration > 0 && stopWatch.ElapsedMilliseconds >= duration) { break; } } } } /// <summary> /// Checks if cancelled. /// </summary> /// <param name="cancelToken">The cancel token.</param> private static void CheckIfCancelled(CancellationToken cancellationToken) { if (cancellationToken == null) throw new ArgumentNullException("cancellationToken"); cancellationToken.ThrowIfCancellationRequested(); } } }
Keluaran:
2/18/2013 4:17:13 PM 2/18/2013 4:17:15 PM 2/18/2013 4:17:17 PM 2/18/2013 4:17:19 PM 2/18/2013 4:17:21 PM 2/18/2013 4:17:23 PM 2/18/2013 4:17:25 PM 2/18/2013 4:17:27 PM 2/18/2013 4:17:29 PM 2/18/2013 4:17:31 PM Finished! Press any key to continue . . .
sumber
TaskScheduler.FromCurrentSynchronizationContext()
sebelum pengaturanmainAction
. Saya kemudian meneruskan penjadwal yang dihasilkan keMainPeriodicTaskAction
dalamnya untuk membuatsubTask
dengan.Ini tidak persis dalam
System.Threading.Tasks
, tetapiObservable.Timer
(atau lebih sederhanaObservable.Interval
) dari perpustakaan Ekstensi Reaktif mungkin adalah apa yang Anda cari.sumber
Sampai sekarang saya menggunakan tugas LongRunning TPL untuk pekerjaan latar belakang terikat CPU siklik alih-alih pengatur waktu threading, karena:
Namun, solusi TPL selalu mengklaim utas khusus yang tidak diperlukan sambil menunggu tindakan berikutnya (yang paling sering). Saya ingin menggunakan solusi yang diusulkan Jeff untuk melakukan pekerjaan siklik terikat CPU di latar belakang karena hanya memerlukan utas threadpool ketika ada pekerjaan yang harus dilakukan yang lebih baik untuk skalabilitas (terutama bila periode intervalnya besar).
Untuk mencapai itu, saya menyarankan 4 adaptasi:
ConfigureAwait(false)
keTask.Delay()
untuk mengeksekusidoWork
tindakan pada utas kumpulan utas, jika tidakdoWork
akan dilakukan pada utas pemanggil yang bukan merupakan gagasan paralelismedoWork
untuk mengaktifkannya membatalkan tugasTentang poin 2 Saya tidak yakin, apakah async menunggu masih memerlukan TaskCanceledExecption atau itu hanya praktik terbaik?
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken) { do { await Task.Delay(period, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); doWork(taskState, cancellationToken); } while (true); }
Tolong berikan komentar Anda untuk solusi yang diusulkan ...
Perbarui 2016-8-30
Solusi di atas tidak segera memanggil
doWork()
tetapi dimulai denganawait Task.Delay().ConfigureAwait(false)
untuk mencapai sakelar utasdoWork()
. Solusi di bawah ini mengatasi masalah ini dengan membungkusdoWork()
panggilan pertama dalam aTask.Run()
dan menunggunya.Di bawah ini adalah pengganti async \ await yang ditingkatkan untuk
Threading.Timer
menjalankan pekerjaan siklik yang dapat dibatalkan dan dapat diskalakan (dibandingkan dengan solusi TPL) karena tidak menempati utas apa pun saat menunggu tindakan berikutnya.Perhatikan bahwa berlawanan dengan Timer, waktu tunggu (
period
) adalah konstan dan bukan waktu siklus; waktu siklus adalah jumlah waktu tunggu dan durasinyadoWork()
dapat bervariasi.public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken) { await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false); do { await Task.Delay(period, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); doWork(taskState, cancellationToken); } while (true); }
sumber
ConfigureAwait(false)
akan menjadwalkan kelanjutan metode ke kumpulan utas, sehingga tidak benar-benar menyelesaikan poin kedua terkait pengatur waktu threading. Saya juga tidak berpikirtaskState
perlu; tangkapan variabel lambda lebih fleksibel dan aman untuk jenis.await Task.Delay()
dandoWork()
begitudoWork()
akan segera mengeksekusi selama startup. Tetapi tanpa beberapa trikdoWork()
akan mengeksekusi thread pemanggil untuk pertama kalinya dan memblokirnya. Stephen, apa kamu punya solusi untuk masalah itu?Task.Run
.Saya perlu memicu tugas asinkron berulang dari metode sinkron.
public static class PeriodicTask { public static async Task Run( Func<Task> action, TimeSpan period, CancellationToken cancellationToken = default(CancellationToken)) { while (!cancellationToken.IsCancellationRequested) { Stopwatch stopwatch = Stopwatch.StartNew(); if (!cancellationToken.IsCancellationRequested) await action(); stopwatch.Stop(); await Task.Delay(period - stopwatch.Elapsed, cancellationToken); } } }
Ini adalah adaptasi dari jawaban Jeff. Itu diubah untuk mengambil dalam
Func<Task>
Itu juga memastikan bahwa periode adalah seberapa sering itu dijalankan dengan mengurangi waktu berjalan tugas dari periode untuk penundaan berikutnya.class Program { static void Main(string[] args) { PeriodicTask .Run(GetSomething, TimeSpan.FromSeconds(3)) .GetAwaiter() .GetResult(); } static async Task GetSomething() { await Task.Delay(TimeSpan.FromSeconds(1)); Console.WriteLine($"Hi {DateTime.UtcNow}"); } }
sumber
Saya mengalami masalah serupa dan menulis
TaskTimer
kelas yang mengembalikan serangkaian tugas yang selesai pada timer: https://github.com/ikriv/tasktimer/ .using (var timer = new TaskTimer(1000).Start()) { // Call DoStuff() every second foreach (var task in timer) { await task; DoStuff(); } }
sumber
static class Helper { public async static Task ExecuteInterval(Action execute, int millisecond, IWorker worker) { while (worker.Worked) { execute(); await Task.Delay(millisecond); } } } interface IWorker { bool Worked { get; } }
Sederhana...
sumber