Sinkron menunggu Tugas <T> selesai dengan batas waktu

388

Saya ingin menunggu Tugas <T> diselesaikan dengan beberapa aturan khusus: Jika belum selesai setelah X milidetik, saya ingin menampilkan pesan kepada pengguna. Dan jika belum selesai setelah Y milidetik, saya ingin secara otomatis meminta pembatalan .

Saya bisa menggunakan Task.ContinueWith untuk secara asinkron menunggu tugas selesai (yaitu menjadwalkan tindakan yang akan dieksekusi ketika tugas selesai), tetapi itu tidak memungkinkan untuk menentukan batas waktu. Saya bisa menggunakan Task.Wait untuk secara sinkron menunggu tugas selesai dengan batas waktu, tetapi itu memblokir utas saya. Bagaimana saya bisa tidak sinkron menunggu tugas selesai dengan batas waktu?

dtb
sumber
3
Kamu benar. Saya terkejut itu tidak memberikan batas waktu. Mungkin di .NET 5.0 ... Tentu saja kita dapat membangun batas waktu ke dalam tugas itu sendiri tetapi itu tidak baik, hal-hal seperti itu harus gratis.
Aliostad
4
Meskipun masih membutuhkan logika untuk batas waktu dua tingkat yang Anda jelaskan, .NET 4.5 memang menawarkan metode sederhana untuk membuat berbasis batas waktu CancellationTokenSource. Tersedia dua overload ke konstruktor, satu mengambil bilangan bulat milidetik integer dan satu mengambil penundaan TimeSpan.
patridge
Sumber lib sederhana yang lengkap di sini: stackoverflow.com/questions/11831844/...
ada solusi akhir dengan kode sumber lengkap yang berfungsi? mungkin sampel yang lebih kompleks untuk memberitahukan kesalahan di setiap utas dan setelah WaitAll menunjukkan ringkasan?
Kiquenet

Jawaban:

566

Bagaimana dengan ini:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

Dan di sini ada posting blog yang bagus "Crafting a Task.TimeoutAfter Method" (dari tim MS Parallel Library) dengan info lebih lanjut tentang hal semacam ini .

Tambahan : atas permintaan komentar pada jawaban saya, berikut ini adalah solusi diperluas yang mencakup penanganan pembatalan. Perhatikan bahwa meneruskan pembatalan ke tugas dan timer berarti bahwa ada beberapa cara pembatalan dapat dialami dalam kode Anda, dan Anda harus yakin untuk menguji dan yakin Anda menangani semuanya dengan benar. Jangan biarkan berbagai kombinasi kebetulan dan berharap komputer Anda melakukan hal yang benar saat runtime.

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}
Andrew Arnott
sumber
86
Harus disebutkan bahwa meskipun Task.Delay dapat menyelesaikan sebelum tugas berjalan lama, memungkinkan Anda untuk menangani skenario batas waktu, ini TIDAK membatalkan tugas berjalan lama itu sendiri; WhenAny hanya memberi tahu Anda bahwa salah satu tugas yang diteruskan telah selesai. Anda harus menerapkan PembatalanToken dan membatalkan sendiri tugas yang sudah berjalan lama.
Jeff Schumacher
30
Dapat juga dicatat bahwa Task.Delaytugas didukung oleh pengatur waktu sistem yang akan terus dilacak sampai batas waktu berakhir terlepas dari berapa lama SomeOperationAsync. Jadi, jika potongan kode keseluruhan ini banyak dieksekusi dalam loop ketat, Anda menghabiskan sumber daya sistem untuk pengatur waktu hingga habis waktu. Cara untuk memperbaikinya adalah dengan CancellationTokenmengirimkannya ke Task.Delay(timeout, cancellationToken)yang Anda batalkan saat SomeOperationAsyncselesai untuk melepaskan sumber daya penghitung waktu.
Andrew Arnott
12
Kode pembatalan melakukan waaaay terlalu banyak pekerjaan. Coba ini: int timeout = 1000; var cancellationTokenSource = PembatalanTokenSource baru (batas waktu); var cancellationToken = tokenSource.Token; var task = SomeOperationAsync (cancellationToken); coba {menunggu tugas; // Tambahkan kode di sini untuk penyelesaian yang berhasil} catch (OperationCancelledException) {// Tambahkan kode di sini untuk kasus batas waktu}
srm
3
@ canans dengan menunggu Task, pengecualian apa pun yang disimpan oleh tugas di-rethrown pada saat itu. Ini memberi Anda kesempatan untuk menangkap OperationCanceledException(jika dibatalkan) atau pengecualian lainnya (jika salah).
Andrew Arnott
3
@TomexOu: pertanyaannya adalah bagaimana cara secara tidak sinkron menunggu penyelesaian Task. Task.Wait(timeout)akan secara sinkron memblokir bukannya menunggu secara tidak sinkron.
Andrew Arnott
221

Berikut adalah versi metode ekstensi yang menyertakan pembatalan batas waktu ketika tugas asli selesai seperti yang disarankan oleh Andrew Arnott dalam komentar untuk jawabannya .

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) {

    using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {

        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
        if (completedTask == task) {
            timeoutCancellationTokenSource.Cancel();
            return await task;  // Very important in order to propagate exceptions
        } else {
            throw new TimeoutException("The operation has timed out.");
        }
    }
}
Lawrence Johnston
sumber
8
Berikan orang ini suara. Solusi elegan. Dan jika Anda menelepon tidak memiliki tipe kembali pastikan Anda hanya menghapus TResult.
Lucas
6
PembatalanTokenSource adalah sekali pakai dan harus di usingblok
PeterM
6
@ It'satrap Menunggu tugas dua kali cukup mengembalikan hasilnya pada menunggu kedua. Itu tidak mengeksekusi dua kali. Anda bisa mengatakan bahwa itu sama dengan task.Result ketika dieksekusi dua kali.
M. Mimpen
7
Akankah tugas asli ( task) masih terus berjalan jika terjadi batas waktu?
Jag
6
Peluang peningkatan kecil: TimeoutExceptionmemiliki pesan default yang sesuai. Mengatasinya dengan "Operasi telah kehabisan waktu." tidak menambah nilai dan sebenarnya menyebabkan kebingungan dengan menyiratkan ada alasan untuk menimpanya.
Edward Brey
49

Anda dapat menggunakan Task.WaitAnyuntuk menunggu yang pertama dari banyak tugas.

Anda dapat membuat dua tugas tambahan (yang selesai setelah batas waktu yang ditentukan) dan kemudian gunakan WaitAnyuntuk menunggu mana yang lebih dulu selesai. Jika tugas yang diselesaikan pertama adalah tugas "pekerjaan" Anda, maka Anda selesai. Jika tugas yang diselesaikan pertama adalah tugas batas waktu, maka Anda dapat bereaksi terhadap batas waktu tersebut (mis. Permintaan pembatalan).

Tomas Petricek
sumber
1
Saya telah melihat teknik ini digunakan oleh seorang MVP yang sangat saya hormati, tampaknya jauh lebih bersih bagi saya daripada jawaban yang diterima. Mungkin contoh akan membantu mendapatkan lebih banyak suara! Saya akan secara sukarela melakukannya kecuali saya tidak memiliki pengalaman
tugas yang
3
satu utas akan diblokir - tetapi jika Anda setuju dengan itu maka tidak ada masalah. Solusi yang saya ambil adalah yang di bawah, karena tidak ada utas yang diblokir. Saya membaca posting blog yang sangat bagus.
JJschk
@ JJschk Anda menyebutkan Anda mengambil solusinya below.... yang itu? berdasarkan pemesanan SO?
BozoJoe
dan bagaimana jika saya tidak ingin tugas yang lebih lambat dibatalkan? Saya ingin menanganinya ketika sudah selesai tetapi kembali dari metode saat ini ..
Akmal Salikhov
18

Bagaimana dengan sesuatu yang seperti ini?

    const int x = 3000;
    const int y = 1000;

    static void Main(string[] args)
    {
        // Your scheduler
        TaskScheduler scheduler = TaskScheduler.Default;

        Task nonblockingTask = new Task(() =>
            {
                CancellationTokenSource source = new CancellationTokenSource();

                Task t1 = new Task(() =>
                    {
                        while (true)
                        {
                            // Do something
                            if (source.IsCancellationRequested)
                                break;
                        }
                    }, source.Token);

                t1.Start(scheduler);

                // Wait for task 1
                bool firstTimeout = t1.Wait(x);

                if (!firstTimeout)
                {
                    // If it hasn't finished at first timeout display message
                    Console.WriteLine("Message to user: the operation hasn't completed yet.");

                    bool secondTimeout = t1.Wait(y);

                    if (!secondTimeout)
                    {
                        source.Cancel();
                        Console.WriteLine("Operation stopped!");
                    }
                }
            });

        nonblockingTask.Start();
        Console.WriteLine("Do whatever you want...");
        Console.ReadLine();
    }

Anda dapat menggunakan opsi Task.Wait tanpa memblokir utas menggunakan Tugas lain.

as-cii
sumber
Sebenarnya dalam contoh ini Anda tidak menunggu di dalam t1 tetapi pada tugas atas. Saya akan mencoba membuat contoh yang lebih rinci.
as-cii
14

Berikut adalah contoh yang berfungsi sepenuhnya berdasarkan jawaban yang dipilih teratas, yaitu:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

Keuntungan utama dari implementasi dalam jawaban ini adalah bahwa obat generik telah ditambahkan, sehingga fungsi (atau tugas) dapat mengembalikan nilai. Ini berarti bahwa setiap fungsi yang ada dapat dibungkus dengan fungsi batas waktu, misalnya:

Sebelum:

int x = MyFunc();

Setelah:

// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));

Kode ini membutuhkan .NET 4.5.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskTimeout
{
    public static class Program
    {
        /// <summary>
        ///     Demo of how to wrap any function in a timeout.
        /// </summary>
        private static void Main(string[] args)
        {

            // Version without timeout.
            int a = MyFunc();
            Console.Write("Result: {0}\n", a);
            // Version with timeout.
            int b = TimeoutAfter(() => { return MyFunc(); },TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", b);
            // Version with timeout (short version that uses method groups). 
            int c = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", c);

            // Version that lets you see what happens when a timeout occurs.
            try
            {               
                int d = TimeoutAfter(
                    () =>
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(123));
                        return 42;
                    },
                    TimeSpan.FromSeconds(1));
                Console.Write("Result: {0}\n", d);
            }
            catch (TimeoutException e)
            {
                Console.Write("Exception: {0}\n", e.Message);
            }

            // Version that works on tasks.
            var task = Task.Run(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return 42;
            });

            // To use async/await, add "await" and remove "GetAwaiter().GetResult()".
            var result = task.TimeoutAfterAsync(TimeSpan.FromSeconds(2)).
                           GetAwaiter().GetResult();

            Console.Write("Result: {0}\n", result);

            Console.Write("[any key to exit]");
            Console.ReadKey();
        }

        public static int MyFunc()
        {
            return 42;
        }

        public static TResult TimeoutAfter<TResult>(
            this Func<TResult> func, TimeSpan timeout)
        {
            var task = Task.Run(func);
            return TimeoutAfterAsync(task, timeout).GetAwaiter().GetResult();
        }

        private static async Task<TResult> TimeoutAfterAsync<TResult>(
            this Task<TResult> task, TimeSpan timeout)
        {
            var result = await Task.WhenAny(task, Task.Delay(timeout));
            if (result == task)
            {
                // Task completed within timeout.
                return task.GetAwaiter().GetResult();
            }
            else
            {
                // Task timed out.
                throw new TimeoutException();
            }
        }
    }
}

Peringatan

Setelah memberikan jawaban ini, biasanya bukan praktik yang baik untuk memiliki pengecualian yang dimasukkan dalam kode Anda selama operasi normal, kecuali Anda benar-benar harus:

  • Setiap kali pengecualian dilemparkan, ini merupakan operasi yang sangat berat,
  • Pengecualian dapat memperlambat kode Anda dengan faktor 100 atau lebih jika pengecualian dalam loop ketat.

Gunakan hanya kode ini jika Anda benar-benar tidak dapat mengubah fungsi yang Anda panggil sehingga waktu habis setelah spesifik TimeSpan.

Jawaban ini benar-benar hanya berlaku ketika berhadapan dengan perpustakaan perpustakaan pihak ke-3 yang Anda tidak bisa menolak untuk memasukkan parameter batas waktu.

Cara menulis kode yang kuat

Jika Anda ingin menulis kode yang kuat, aturan umumnya adalah ini:

Setiap operasi yang berpotensi memblokir tanpa batas waktu, harus memiliki batas waktu.

Jika Anda tidak mematuhi aturan ini, kode Anda pada akhirnya akan mengenai operasi yang gagal karena suatu alasan, maka itu akan diblokir tanpa batas waktu, dan aplikasi Anda baru saja digantung secara permanen.

Jika ada batas waktu yang wajar setelah beberapa waktu, maka aplikasi Anda akan hang selama beberapa waktu yang ekstrem (misalnya 30 detik) maka ia akan menampilkan kesalahan dan melanjutkan dengan cara riang, atau coba lagi.

Contango
sumber
11

Menggunakan pustaka AsyncEx Stephen Cleary yang sangat baik , Anda dapat melakukan:

TimeSpan timeout = TimeSpan.FromSeconds(10);

using (var cts = new CancellationTokenSource(timeout))
{
    await myTask.WaitAsync(cts.Token);
}

TaskCanceledException akan dilemparkan jika terjadi timeout.

Cocowalla
sumber
10

Ini adalah versi yang sedikit lebih baik dari jawaban sebelumnya.

  • Selain jawaban Lawrence , itu membatalkan tugas asli ketika batas waktu terjadi.
  • Sebagai tambahan untuk varian jawaban sjb 2 dan 3 , Anda dapat menyediakan CancellationTokenuntuk tugas asli, dan ketika batas waktu terjadi, Anda mendapatkan TimeoutExceptionalih-alih OperationCanceledException.
async Task<TResult> CancelAfterAsync<TResult>(
    Func<CancellationToken, Task<TResult>> startTask,
    TimeSpan timeout, CancellationToken cancellationToken)
{
    using (var timeoutCancellation = new CancellationTokenSource())
    using (var combinedCancellation = CancellationTokenSource
        .CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
    {
        var originalTask = startTask(combinedCancellation.Token);
        var delayTask = Task.Delay(timeout, timeoutCancellation.Token);
        var completedTask = await Task.WhenAny(originalTask, delayTask);
        // Cancel timeout to stop either task:
        // - Either the original task completed, so we need to cancel the delay task.
        // - Or the timeout expired, so we need to cancel the original task.
        // Canceling will not affect a task, that is already completed.
        timeoutCancellation.Cancel();
        if (completedTask == originalTask)
        {
            // original task completed
            return await originalTask;
        }
        else
        {
            // timeout
            throw new TimeoutException();
        }
    }
}

Pemakaian

InnerCallAsyncmungkin butuh waktu lama untuk selesai. CallAsyncmembungkusnya dengan batas waktu.

async Task<int> CallAsync(CancellationToken cancellationToken)
{
    var timeout = TimeSpan.FromMinutes(1);
    int result = await CancelAfterAsync(ct => InnerCallAsync(ct), timeout,
        cancellationToken);
    return result;
}

async Task<int> InnerCallAsync(CancellationToken cancellationToken)
{
    return 42;
}
Josef Bláha
sumber
1
Terima kasih atas solusinya! Sepertinya Anda harus timeoutCancellationmasuk delayTask. Saat ini, jika Anda memecat pembatalan, CancelAfterAsyncbisa melempar TimeoutExceptionbukan TaskCanceledException, sebab delayTaskmungkin selesai dulu.
AxelUser
@AxelUser, Anda benar. Butuh satu jam dengan banyak unit tes untuk memahami apa yang terjadi :) Saya berasumsi bahwa ketika kedua tugas yang diberikan WhenAnydibatalkan dengan token yang sama, WhenAnyakan mengembalikan tugas pertama. Asumsi itu salah. Saya sudah mengedit jawabannya. Terima kasih!
Josef Bláha
Saya mengalami kesulitan mencari tahu bagaimana sebenarnya memanggil ini dengan fungsi <SomeResult> Tugas yang ditentukan; setiap kesempatan Anda bisa menempel pada contoh bagaimana menyebutnya?
jhaagsma
1
@jhaagsma, contoh ditambahkan!
Josef Bláha
@ JosefBláha Terima kasih banyak! Saya masih perlahan membungkus kepala saya di sekitar sintaks gaya lambda, yang tidak akan terjadi pada saya - bahwa token diteruskan ke tugas dalam tubuh CancelAfterAsync, dengan melewati fungsi lambda. Bagus!
jhaagsma
8

Gunakan Timer untuk menangani pesan dan pembatalan otomatis. Saat Tugas selesai, hubungi Buang penghitung waktu agar mereka tidak akan pernah menyala. Berikut ini sebuah contoh; ubah taskDelay menjadi 500, 1500, atau 2500 untuk melihat berbagai kasus:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        private static Task CreateTaskWithTimeout(
            int xDelay, int yDelay, int taskDelay)
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;
            var task = Task.Factory.StartNew(() =>
            {
                // Do some work, but fail if cancellation was requested
                token.WaitHandle.WaitOne(taskDelay);
                token.ThrowIfCancellationRequested();
                Console.WriteLine("Task complete");
            });
            var messageTimer = new Timer(state =>
            {
                // Display message at first timeout
                Console.WriteLine("X milliseconds elapsed");
            }, null, xDelay, -1);
            var cancelTimer = new Timer(state =>
            {
                // Display message and cancel task at second timeout
                Console.WriteLine("Y milliseconds elapsed");
                cts.Cancel();
            }
                , null, yDelay, -1);
            task.ContinueWith(t =>
            {
                // Dispose the timers when the task completes
                // This will prevent the message from being displayed
                // if the task completes before the timeout
                messageTimer.Dispose();
                cancelTimer.Dispose();
            });
            return task;
        }

        static void Main(string[] args)
        {
            var task = CreateTaskWithTimeout(1000, 2000, 2500);
            // The task has been started and will display a message after
            // one timeout and then cancel itself after the second
            // You can add continuations to the task
            // or wait for the result as needed
            try
            {
                task.Wait();
                Console.WriteLine("Done waiting for task");
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Error waiting for task:");
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}

Juga, CTP Async menyediakan metode TaskEx.Delay yang akan membungkus pengatur waktu dalam tugas untuk Anda. Ini dapat memberi Anda lebih banyak kontrol untuk melakukan hal-hal seperti mengatur TaskScheduler untuk kelanjutan ketika Timer menyala.

private static Task CreateTaskWithTimeout(
    int xDelay, int yDelay, int taskDelay)
{
    var cts = new CancellationTokenSource();
    var token = cts.Token;
    var task = Task.Factory.StartNew(() =>
    {
        // Do some work, but fail if cancellation was requested
        token.WaitHandle.WaitOne(taskDelay);
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task complete");
    });

    var timerCts = new CancellationTokenSource();

    var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
    messageTask.ContinueWith(t =>
    {
        // Display message at first timeout
        Console.WriteLine("X milliseconds elapsed");
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
    cancelTask.ContinueWith(t =>
    {
        // Display message and cancel task at second timeout
        Console.WriteLine("Y milliseconds elapsed");
        cts.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    task.ContinueWith(t =>
    {
        timerCts.Cancel();
    });

    return task;
}
Quartermeister
sumber
Dia tidak ingin utas saat ini diblokir, yaitu, tidak task.Wait().
Cheng Chen
@Danny: Itu hanya untuk membuat contoh lengkap. Setelah LanjutkanDengan Anda dapat kembali dan membiarkan tugas berjalan. Saya akan memperbarui jawaban saya untuk membuatnya lebih jelas.
Quartermeister
2
@ dtb: Bagaimana jika Anda menjadikan t1 sebagai Tugas <Tugas <Hasil>> lalu panggil TaskExtensions.Unwrap? Anda dapat mengembalikan t2 dari lambda batin Anda, dan Anda dapat menambahkan kelanjutan untuk tugas yang belum dibuka setelahnya.
Quartermeister
Luar biasa! Itu memecahkan masalah saya dengan sempurna. Terima kasih! Saya pikir saya akan pergi dengan solusi yang diusulkan oleh @ AS-CII, meskipun saya berharap saya bisa menerima jawaban Anda juga untuk menyarankan TaskExtensions.Unwrap Haruskah saya membuka pertanyaan baru sehingga Anda bisa mendapatkan perwakilan yang Anda pantas?
dtb
6

Cara lain untuk memecahkan masalah ini adalah menggunakan Ekstensi Reaktif:

public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
        return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

Uji di atas menggunakan kode di bawah ini dalam unit test Anda, itu berfungsi untuk saya

TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
                {
                    int i = 0;
                    while (i < 5)
                    {
                        Console.WriteLine(i);
                        i++;
                        Thread.Sleep(1000);
                    }
                })
                .TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
                .ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);

scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);

Anda mungkin memerlukan namespace berikut:

using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;
Kevan
sumber
4

Versi generik jawaban @ Kevan di atas, menggunakan Ekstensi Reaktif.

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
    return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

Dengan Penjadwal opsional:

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
    return scheduler is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

BTW: Ketika Timeout terjadi, eksepsi timeout akan dilempar

Jasper H Bojsen
sumber
0

Jika Anda menggunakan BlockingCollection untuk menjadwalkan tugas, produsen dapat menjalankan tugas yang berpotensi berjalan lama dan konsumen dapat menggunakan metode TryTake yang memiliki batas waktu dan token pembatalan bawaan.

kns98
sumber
Saya harus menulis sesuatu (tidak ingin memasukkan kode hak milik di sini) tetapi skenario seperti ini. Produser akan menjadi kode yang mengeksekusi metode yang bisa time out dan akan memasukkan hasilnya ke dalam antrian ketika selesai. Konsumen akan memanggil trytake () dengan batas waktu dan akan menerima token setelah batas waktu. Baik produsen maupun konsumen akan melakukan tugas-tugas backround dan menampilkan pesan kepada pengguna menggunakan pengirim thread UI jika perlu.
kns98
0

Saya merasakan Task.Delay()tugas danCancellationTokenSource jawaban lain sedikit banyak untuk kasus penggunaan saya dalam loop jaringan yang ketat.

Dan meskipun begitu Joe Hoag's Crafting a Task.TimeoutSetelah Metode di blog MSDN menginspirasi, saya sedikit lelah menggunakanTimeoutException untuk kontrol aliran untuk alasan yang sama seperti di atas, karena timeout diharapkan lebih sering daripada tidak.

Jadi saya pergi dengan ini, yang juga menangani optimasi yang disebutkan di blog:

public static async Task<bool> BeforeTimeout(this Task task, int millisecondsTimeout)
{
    if (task.IsCompleted) return true;
    if (millisecondsTimeout == 0) return false;

    if (millisecondsTimeout == Timeout.Infinite)
    {
        await Task.WhenAll(task);
        return true;
    }

    var tcs = new TaskCompletionSource<object>();

    using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs,
        millisecondsTimeout, Timeout.Infinite))
    {
        return await Task.WhenAny(task, tcs.Task) == task;
    }
}

Contoh use case adalah sebagai berikut:

var receivingTask = conn.ReceiveAsync(ct);

while (!await receivingTask.BeforeTimeout(keepAliveMilliseconds))
{
    // Send keep-alive
}

// Read and do something with data
var data = await receivingTask;
antak
sumber
0

Beberapa varian jawaban Andrew Arnott:

  1. Jika Anda ingin menunggu tugas yang ada dan mencari tahu apakah tugasnya selesai atau waktunya habis, tetapi tidak ingin membatalkannya jika batas waktu terjadi:

    public static async Task<bool> TimedOutAsync(this Task task, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        if (timeoutMilliseconds == 0) {
            return !task.IsCompleted; // timed out if not completed
        }
        var cts = new CancellationTokenSource();
        if (await Task.WhenAny( task, Task.Delay(timeoutMilliseconds, cts.Token)) == task) {
            cts.Cancel(); // task completed, get rid of timer
            await task; // test for exceptions or task cancellation
            return false; // did not timeout
        } else {
            return true; // did timeout
        }
    }
  2. Jika Anda ingin memulai tugas kerja dan membatalkan pekerjaan jika batas waktu terjadi:

    public static async Task<T> CancelAfterAsync<T>( this Func<CancellationToken,Task<T>> actionAsync, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var taskCts = new CancellationTokenSource();
        var timerCts = new CancellationTokenSource();
        Task<T> task = actionAsync(taskCts.Token);
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }
  3. Jika Anda memiliki tugas yang sudah dibuat yang ingin Anda batalkan jika terjadi timeout:

    public static async Task<T> CancelAfterAsync<T>(this Task<T> task, int timeoutMilliseconds, CancellationTokenSource taskCts)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var timerCts = new CancellationTokenSource();
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }

Komentar lain, versi ini akan membatalkan timer jika batas waktu tidak terjadi, sehingga beberapa panggilan tidak akan menyebabkan timer menumpuk.

sjb

sjb-sjb
sumber
0

Saya menggabungkan ide-ide dari beberapa jawaban lain di sini dan jawaban ini di utas lain menjadi metode ekstensi gaya Try. Ini memiliki manfaat jika Anda menginginkan metode ekstensi, namun menghindari pengecualian pada batas waktu.

public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task,
    TimeSpan timeout, Action<TResult> successor)
{

    using var timeoutCancellationTokenSource = new CancellationTokenSource();
    var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token))
                                  .ConfigureAwait(continueOnCapturedContext: false);

    if (completedTask == task)
    {
        timeoutCancellationTokenSource.Cancel();

        // propagate exception rather than AggregateException, if calling task.Result.
        var result = await task.ConfigureAwait(continueOnCapturedContext: false);
        successor(result);
        return true;
    }
    else return false;        
}     

async Task Example(Task<string> task)
{
    string result = null;
    if (await task.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), r => result = r))
    {
        Console.WriteLine(result);
    }
}    
tm1
sumber
-3

Jelas tidak melakukan ini, tetapi itu adalah pilihan jika ... Saya tidak bisa memikirkan alasan yang sah.

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
syko9000
sumber