Eşzamansız olarak <T> Görevi'nin zaman aşımı ile tamamlanmasını bekleyin


387

Görev <T> ' nin bazı özel kurallarla tamamlanmasını beklemek istiyorum : X milisaniyeden sonra tamamlanmadıysa, kullanıcıya bir mesaj görüntülemek istiyorum. Ve Y milisaniyeden sonra tamamlanmadıysa, otomatik olarak iptal isteğinde bulunmak istiyorum .

Görevin tamamlanmasını beklemek için Task.ContinueWith kullanabilirsiniz (yani görev tamamlandığında yürütülecek bir eylem zamanlamak), ancak bu bir zaman aşımı belirtmeye izin vermez. Görev zaman aşımı ile tamamlamak için eşzamanlı olarak beklemek için Task.Wait kullanabilirsiniz , ancak bu benim iş parçacığı engeller. Görevin bir zaman aşımı ile tamamlanmasını eşzamansız olarak nasıl bekleyebilirim?


3
Haklısın. Zaman aşımı sağlamadığı için şaşırdım. Belki .NET 5.0'da ... Tabii ki zaman aşımını görevin içine inşa edebiliriz ama bu iyi değil, bu tür şeyler özgür olmalı.
Aliostad

4
Tanımladığınız iki katmanlı zaman aşımı için yine de mantık gerektirse de, .NET 4.5 gerçekten de zaman aşımı tabanlı bir yöntem oluşturmak için basit bir yöntem sunar CancellationTokenSource. Biri tamsayı milisaniye gecikmesi ve diğeri TimeSpan gecikmesi alan iki yapıcıya aşırı yüklenme mevcuttur.
patridge

Tam basit lib kaynağı burada: stackoverflow.com/questions/11831844/…

tam kaynak kodu ile herhangi bir son çözüm çalışıyor? her iş parçacığı ve WaitAll bir özet gösterir sonra hataları bildirmek için belki daha karmaşık bir örnek?
Kiquenet

Yanıtlar:


565

Buna ne dersin:

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

Ve işte bu tür şeyler hakkında daha fazla bilgi içeren "MS Parallel Library ekibinden" bir Task.TimeoutAfter Metodu Oluşturma " adlı harika bir blog yazısı .

Ek : cevabımla ilgili yorum yapılması gerektiğinde, iptal işlemeyi içeren genişletilmiş bir çözüm. İptal işleminin görev ve zamanlayıcıya geçirilmesinin, kodunuzda iptal işleminin yaşanabileceği birden çok yol olduğu anlamına geldiğini ve hepsini doğru bir şekilde test ettiğinizden ve emin olduğunuzdan emin olmanız gerektiğini unutmayın. Çeşitli kombinasyonları şansa bırakmayın ve bilgisayarınızın çalışma zamanında doğru şeyi yapmasını umuyoruz.

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
}

86
Task.Delay'ın uzun süre çalışmadan önce tamamlanabilmesine ve zaman aşımı senaryosunu işlemenize izin vermesine rağmen, bunun uzun süren görevin kendisini iptal ETMEMESİ gerektiği belirtilmelidir; WhenAny, kendisine verilen görevlerden birinin tamamlandığını bilmenizi sağlar. Bir CancellationToken uygulamanız ve uzun süren görevi kendiniz iptal etmeniz gerekecektir.
Jeff Schumacher

30
Task.DelayGörevin, ne kadar SomeOperationAsyncsürdüğüne bakılmaksızın zaman aşımı süresi dolana kadar izlenmeye devam edecek bir sistem zamanlayıcısı tarafından desteklendiği de not edilebilir . Dolayısıyla, bu genel kod snippet'i sıkı bir döngüde çok şey yürütüyorsa, zamanlayıcılar için zaman aşımına kadar sistem kaynaklarını tüketirsiniz. Bunu düzeltmenin yolu , zamanlayıcı kaynağını serbest bırakmak için tamamladığınızda iptal CancellationTokenettiğiniz bir iletiye sahip olmaktır . Task.Delay(timeout, cancellationToken)SomeOperationAsync
Andrew Arnott

12
İptal kodu çok fazla iş yapıyor. Bunu deneyin: int timeout = 1000; var cancellationTokenSource = yeni CancellationTokenSource (zaman aşımı); var cancellationToken = tokenSource.Token; var task = SomeOperationAsync (cancellationToken); denemek {görevi beklemek; // Başarılı tamamlama için kodu buraya ekleyin} catch (OperationCancelledException) {// Zaman aşımı durumu için kodu buraya ekleyin}
srm

3
@ilans bekleyerek, Taskgörev tarafından saklanan herhangi bir istisna o noktada yeniden oluşturulur. Bu size OperationCanceledException(iptal edilirse) veya başka bir istisna (hatalıysa) yakalama şansı verir .
Andrew Arnott

3
@TomexOu: soru, bir Görev'in tamamlanmasını eşzamansız olarak nasıl bekleyeceğiydi. Task.Wait(timeout)eşzamansız olarak beklemek yerine eşzamanlı olarak engellenir.
Andrew Arnott

221

İşte Andrew Arnott'un cevabına yaptığı bir yorumda önerildiği gibi orijinal görev tamamlandığında zaman aşımının iptalini içeren bir uzantı yöntemi sürümü .

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.");
        }
    }
}

8
Bu adama biraz oy verin. Zarif çözüm. Ayrıca, aramanızın dönüş türü yoksa, TResult'u kaldırdığınızdan emin olun.
Lucas

6
using
TokenSource

6
@ It'satrap Bir görevi iki kez beklemek ikinci beklemede sonucu döndürür. İki kez çalışmaz. task.Result İki kez idam edildiğinde buna eşit olduğunu söyleyebilirsiniz .
M. Mimpen

7
taskZaman aşımı durumunda orijinal görev ( ) çalışmaya devam eder mi?
jag

6
Küçük iyileştirme fırsatı: TimeoutExceptionuygun bir varsayılan iletiye sahiptir. "İşlem zaman aşımına uğradı" ile geçersiz kılındı. hiçbir değer katmaz ve onu geçersiz kılmak için bir neden olduğunu ima ederek bazı karışıklıklara neden olur.
Edward Brey

49

Task.WaitAnyBirden fazla görevin ilki beklemek için kullanabilirsiniz .

İki ek görev (belirtilen zaman aşımlarından sonra tamamlanan) oluşturabilir ve sonra WaitAnyhangisinin önce tamamlanacağını beklemek için kullanabilirsiniz . İlk tamamlanan görev sizin "iş" görevinizse, işiniz bitmiştir. İlk olarak tamamlanan görev bir zaman aşımı göreviyse, zaman aşımına tepki verebilirsiniz (örn. İstek iptali).


1
Bir MVP tarafından kullanılan bu tekniği gerçekten saygı duydum, benim için kabul edilen cevaptan çok daha temiz görünüyor. Belki bir örnek daha fazla oy almanıza yardımcı olabilir! Yararlı olacağından emin olmak için yeterli Görev deneyimine sahip olmam dışında bunu yapmak için gönüllü olurum :)
GrahamMc

3
bir iş parçacığı bloke olurdu - ama eğer ur ok o zaman sorun yok. Aldığım çözüm, hiçbir iş parçacığı bloke olmadığından aşağıdaki çözümdü. Gerçekten iyi olan blog gönderisini okudum.
JJschk

@JJschk çözümü aldığınızdan bahsediyorsunuz below.... bu da nedir? SO sipariş dayalı?
BozoJoe

ve daha yavaş bir görevin iptal edilmesini istemezsem ne olur?
Bittiği

18

Böyle bir şeye ne dersin?

    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();
    }

Başka bir Görev kullanarak ana iş parçacığını engellemeden Task.Wait seçeneğini kullanabilirsiniz.


Aslında bu örnekte t1'in içinde değil, bir üst görevde bekliyorsunuz. Daha ayrıntılı bir örnek vermeye çalışacağım.
as-cii

14

İşte en çok oy alan cevaba dayanan tamamen çalışan bir örnek:

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

Bu cevaptaki uygulamanın ana avantajı jeneriklerin eklenmiş olmasıdır, böylece işlev (veya görev) bir değer döndürebilir. Bu, varolan herhangi bir işlevin zaman aşımı işleviyle sarılabileceği anlamına gelir, örneğin:

Önce:

int x = MyFunc();

Sonra:

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

Bu kod .NET 4.5 gerektirir.

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();
            }
        }
    }
}

Uyarılar

Bu cevabı verdikten sonra , kesinlikle yapmanız gerekmedikçe, normal çalışma sırasında kodunuzda istisnalar olması iyi bir uygulama değildir :

  • Her istisna atıldığında, son derece ağır bir işlemdir,
  • İstisnalar sıkı bir döngüde ise istisnalar kodunuzu 100 veya daha fazla faktör yavaşlatabilir.

Bu kodu yalnızca, aradığınız işlevi kesinlikle değiştiremiyorsanız kullanın, böylece belirli bir süreden sonra zaman aşımına uğrar TimeSpan.

Bu cevap gerçekten sadece bir zaman aşımı parametresi eklemek için yeniden düzenleyemeyeceğiniz 3. taraf kütüphane kütüphaneleri ile uğraşırken geçerlidir.

Sağlam kod nasıl yazılır

Sağlam kod yazmak istiyorsanız, genel kural şudur:

Süresiz olarak engellenebilecek her işlemde zaman aşımı olması gerekir.

Eğer varsa yok bu kurala, kodunuz sonunda nedense, o zaman süresiz engeller başarısız olur ve uygulama sadece kalıcı asılı olan bir operasyonu vuracaktır.

Bir süre sonra makul bir zaman aşımı olsaydı, uygulamanız aşırı bir süre beklerdi (örneğin 30 saniye), bir hata görüntüler ve neşeli yoluna devam eder veya tekrar dener.


11

Stephen Cleary'nin mükemmel AsyncEx kütüphanesini kullanarak şunları yapabilirsiniz:

TimeSpan timeout = TimeSpan.FromSeconds(10);

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

TaskCanceledException zaman aşımı durumunda atılır.


10

Bu, önceki cevapların biraz geliştirilmiş bir sürümüdür.

  • Lawrence'ın cevabına ek olarak , zaman aşımı oluştuğunda orijinal görevi iptal eder.
  • Sjb'nin cevap varyantları 2 ve 3'e ek olarak CancellationToken, orijinal görevi sağlayabilirsiniz ve zaman aşımı oluştuğunda TimeoutExceptionbunun yerine alırsınız 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();
        }
    }
}

kullanım

InnerCallAsynctamamlanması uzun zaman alabilir. CallAsynczaman aşımı ile sarar.

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;
}

1
Çözüm için teşekkürler! timeoutCancellationİçine geçmelisin gibi görünüyor delayTask. Şu anda, yangın iptal ederseniz, yerine CancelAfterAsyncatmak , neden önce bitmiş olabilir. TimeoutExceptionTaskCanceledExceptiondelayTask
AxelUser

@AxelUser, haklısın. Ne olduğunu anlamak için bir sürü birim testle bana bir saat sürdü :) Verilen her iki görev WhenAnyaynı jeton tarafından iptal edildiğinde WhenAny, ilk görevi geri alacağını varsaydım . Bu varsayım yanlıştı. Cevabı düzenledim. Teşekkürler!
Josef Bláha

Ben aslında bu tanımlanmış bir Görev <SomeResult> işlevi ile aramak için nasıl anlamaya zor yaşıyorum; nasıl arayacağınıza dair bir örneği ele alma şansınız var mı?
jhaagsma

1
@jhaagsma, örnek eklendi!
Josef Bláha

@ JosefBláha Çok teşekkürler! Hala yavaşça başımı lambda tarzı sözdizimi etrafına sarıyorum, bu bana gelmeyecekti - token lambda işlevini geçirerek CancelAfterAsync gövdesindeki göreve aktarıldı. Şık!
jhaagsma

8

Mesajı işlemek ve otomatik iptal etmek için bir Zamanlayıcı kullanın . Görev tamamlandığında, asla ateş etmemeleri için zamanlayıcıları imha edin. İşte bir örnek; Farklı durumları görmek için 500, 1500 veya 2500 olarak gecikme yapın:

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);
                }
            }
        }
    }
}

Ayrıca, Async CTP , zamanlayıcıları sizin için görevlerde saran bir TaskEx.Delay yöntemi sağlar. Bu, Zamanlayıcı tetiklendiğinde devam etmek için TaskScheduler'ı ayarlamak gibi şeyleri yapmak için daha fazla kontrol sağlayabilir.

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;
}

Geçerli iş parçacığının engellenmesini istemiyor, yani hayır task.Wait().
Cheng Chen

@Danny: Bu sadece örneği tamamlamaktı. Devam ettikten sonra geri dönebilir ve görevin çalışmasına izin verebilirsiniz. Bunu daha açık hale getirmek için cevabımı güncelleyeceğim.
Quartermeister

2
@dtb: t1'i bir Görev <Görev <Sonuç>> yapar ve sonra TaskExtensions.Unwrap öğesini çağırırsanız ne olur? T2'yi iç lambdadan döndürebilir ve daha sonra açılmamış göreve devam edebilirsiniz.
Quartermeister

Müthiş! Bu benim sorunumu mükemmel bir şekilde çözüyor. Teşekkürler! Sanırım @ AS-CII tarafından önerilen çözümle devam edeceğim, ancak keşke de TaskExtensions'ı önermek için cevabınızı kabul edebilseydim.
dtb

6

Bu sorunu çözmenin başka bir yolu da Reaktif Uzantıları kullanmaktır:

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

Ünite testinizde aşağıdaki kodu kullanarak yukarıda test edin, benim için çalışıyor

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);

Aşağıdaki ad alanına ihtiyacınız olabilir:

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;

4

@ Kevan'ın yukarıdaki reaktif uzantıları kullanan cevabının genel bir versiyonu.

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

İsteğe bağlı Zamanlayıcı ile:

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: Zaman Aşımı olduğunda, zaman aşımı istisnası atılır


0

Görevi zamanlamak için bir BlockingCollection kullanırsanız, üretici potansiyel olarak uzun süre çalışan görevi çalıştırabilir ve tüketici yerleşik zaman aşımı ve iptal jetonu içeren TryTake yöntemini kullanabilir.


Bir şey yazmak zorunda kalacaktım (burada özel kod koymak istemiyorum) ama senaryo böyle. Üretici, zaman aşımına uğrayabilecek yöntemi yürüten kod olacaktır ve bittiğinde sonuçları sıraya koyacaktır. Tüketici zaman aşımı ile trytake () öğesini çağırır ve zaman aşımı üzerine jetonu alır. Hem üretici hem de tüketici artalanları olacak ve gerektiğinde kullanıcı arabirimi iş parçacığını kullanan kullanıcıya bir mesaj gösterecektir.
kns98

0

Task.Delay()Görevi hissettim ve CancellationTokenSourcediğer cevaplarda sıkı bir ağ döngüsünde kullanım durumum için biraz fazla cevap verdim.

Joe Hoag'ın MSDN bloglarında bir Görev.TimeoutAfter Metodu ilham verici olmasına rağmen TimeoutException, yukarıdaki gibi aynı nedenden dolayı akış kontrolü için biraz yoruldum , çünkü zaman aşımlarından daha sık bekleniyordu.

Bu yüzden, blogda bahsedilen optimizasyonları da ele alan bu ile gittim:

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;
    }
}

Örnek bir kullanım örneği şöyledir:

var receivingTask = conn.ReceiveAsync(ct);

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

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

0

Andrew Arnott'un cevabının birkaç çeşidi:

  1. Varolan bir görevi beklemek ve tamamlandığını veya zaman aşımına uğradığını öğrenmek, ancak zaman aşımı gerçekleşirse görevi iptal etmek istemiyorsanız:

    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. Bir iş görevi başlatmak ve zaman aşımı oluşursa işi iptal etmek istiyorsanız:

    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. Zaten bir zaman aşımı oluşursa iptal etmek istediğiniz bir göreviniz varsa:

    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
    }

Başka bir yorum, zaman aşımı gerçekleşmezse bu sürümler zamanlayıcıyı iptal eder, bu nedenle birden fazla çağrı zamanlayıcıların yığılmasına neden olmaz.

SJB


0

Burada bazı cevaplar ve Try-style uzantısı yöntemi başka bir iş parçacığında bu cevap fikir şu . Bir uzatma yöntemi istiyorsanız, ancak zaman aşımı durumunda bir istisnadan kaçınırsanız, bunun bir yararı vardır.

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);
    }
}    

-3

Kesinlikle bunu yapmayın, ama eğer bir seçenek ... Geçerli bir sebep düşünemiyorum.

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.