Chờ đồng bộ tác vụ <T> để hoàn thành với thời gian chờ


387

Tôi muốn đợi Nhiệm vụ <T> hoàn thành với một số quy tắc đặc biệt: Nếu nó chưa hoàn thành sau X mili giây, tôi muốn hiển thị thông báo cho người dùng. Và nếu nó chưa hoàn thành sau Y mili giây, tôi muốn tự động yêu cầu hủy .

Tôi có thể sử dụng Task.CContueWith để chờ không đồng bộ để tác vụ hoàn thành (nghĩa là lên lịch cho một hành động được thực hiện khi tác vụ hoàn thành), nhưng điều đó không cho phép chỉ định thời gian chờ. Tôi có thể sử dụng Task.Wait để chờ đồng bộ tác vụ hoàn thành với thời gian chờ, nhưng điều đó chặn luồng của tôi. Làm thế nào tôi có thể chờ đợi đồng bộ nhiệm vụ hoàn thành với thời gian chờ?


3
Bạn đúng rồi. Tôi ngạc nhiên khi nó không cung cấp cho thời gian chờ. Có thể trong .NET 5.0 ... Tất nhiên chúng ta có thể xây dựng thời gian chờ vào chính nhiệm vụ nhưng điều đó là không tốt, những thứ như vậy phải được miễn phí.
Aliostad

4
Mặc dù nó vẫn yêu cầu logic cho thời gian chờ hai tầng mà bạn mô tả, .NET 4.5 thực sự cung cấp một phương pháp đơn giản để tạo thời gian chờ dựa trên thời gian chờ CancellationTokenSource. Hai quá tải cho hàm tạo có sẵn, một lấy độ trễ mili giây và một lấy độ trễ TimeSpan.
patridge

Nguồn lib đơn giản hoàn chỉnh tại đây: stackoverflow.com/questions/11831844/ từ

bất kỳ giải pháp cuối cùng với mã nguồn đầy đủ làm việc? có thể mẫu phức tạp hơn để thông báo lỗi trong mỗi luồng và sau khi WaitAll hiển thị tóm tắt?
Kiquenet

Câu trả lời:


565

Còn cái này thì sao:

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

Và đây là một bài đăng trên blog tuyệt vời "Chế tạo một tác vụ. Phương thức sau khi thực hiện" (từ nhóm Thư viện song song của MS) với nhiều thông tin hơn về loại điều này .

Ngoài ra , theo yêu cầu nhận xét về câu trả lời của tôi, đây là một giải pháp mở rộng bao gồm xử lý hủy bỏ. Lưu ý rằng chuyển hủy cho tác vụ và bộ đếm thời gian có nghĩa là có thể có nhiều cách hủy trong mã của bạn và bạn nên chắc chắn kiểm tra và tự tin rằng bạn xử lý đúng tất cả chúng. Đừng để có cơ hội kết hợp khác nhau và hy vọng máy tính của bạn làm điều đúng đắn trong thời gian chạy.

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
Cần phải đề cập rằng mặc dù Task.Delay có thể hoàn thành trước khi chạy tác vụ dài, cho phép bạn xử lý một kịch bản hết thời gian chờ, nhưng điều này KHÔNG tự hủy bỏ tác vụ chạy dài; Khi nào bạn chỉ cần cho bạn biết rằng một trong những nhiệm vụ được giao cho nó đã hoàn thành. Bạn sẽ phải tự thực hiện CancellingToken và tự hủy tác vụ dài hạn.
Jeff Schumacher

30
Cũng có thể lưu ý rằng Task.Delaytác vụ được hỗ trợ bởi bộ hẹn giờ hệ thống sẽ tiếp tục được theo dõi cho đến khi hết thời gian chờ đợi bất kể SomeOperationAsyncmất bao lâu . Vì vậy, nếu đoạn mã tổng thể này thực thi rất nhiều trong một vòng lặp chặt chẽ, bạn đang tiêu tốn tài nguyên hệ thống cho bộ định thời gian cho đến khi hết thời gian. Cách khắc phục sẽ là có một CancellationTokencái mà bạn chuyển đến Task.Delay(timeout, cancellationToken)mà bạn hủy khi SomeOperationAsynchoàn thành để giải phóng tài nguyên bộ đếm thời gian.
Andrew Arnott

12
Mã hủy đang làm waaaay quá nhiều công việc. Hãy thử điều này: int timeout = 1000; var CancTokenSource = new CancellingTokenSource (hết thời gian); var CancToken = tokenSource.Token; var task = someOperationAsync (hủyToken); thử {chờ đợi nhiệm vụ; // Thêm mã ở đây để hoàn thành thành công} Catch (OperationCancellingException) {// Thêm mã ở đây cho trường hợp hết thời gian}
srm

3
@ilans bằng cách chờ đợi Task, bất kỳ ngoại lệ nào được lưu trữ bởi tác vụ sẽ được truy xuất lại tại thời điểm đó. Điều này cho bạn cơ hội để bắt OperationCanceledException(nếu bị hủy) hoặc bất kỳ ngoại lệ nào khác (nếu bị lỗi).
Andrew Arnott

3
@TomexOu: câu hỏi là làm thế nào để đồng bộ chờ đợi hoàn thành Nhiệm vụ. Task.Wait(timeout)sẽ chặn đồng bộ thay vì chờ đợi không đồng bộ.
Andrew Arnott

221

Đây là phiên bản phương thức mở rộng kết hợp hủy bỏ thời gian chờ khi tác vụ ban đầu hoàn thành theo đề xuất của Andrew Arnott trong một bình luận cho câu trả lời của anh ấy .

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
Cho người đàn ông này một số phiếu. Giải pháp thanh lịch. Và nếu cuộc gọi của bạn không có kiểu trả về, hãy đảm bảo bạn chỉ xóa TResult.
Lucas

6
CancellingTokenSource là dùng một lần và nên ở trong một usingkhối
PeterM

6
@ Itatrap Chờ đợi một nhiệm vụ hai lần chỉ đơn giản là trả về kết quả trong lần chờ thứ hai. Nó không thực hiện hai lần. Bạn có thể nói rằng nó bằng task.Result khi thực hiện hai lần.
M. Mimpen

7
Tác vụ gốc ( task) vẫn sẽ tiếp tục chạy trong trường hợp hết thời gian?
jag

6
Cơ hội cải thiện nhỏ: TimeoutExceptioncó một thông điệp mặc định phù hợp. Ghi đè bằng "Hoạt động đã hết thời gian." không có giá trị và thực sự gây ra một số nhầm lẫn bằng cách ngụ ý có lý do để ghi đè lên nó.
Edward Brey

49

Bạn có thể sử dụng Task.WaitAnyđể chờ đầu tiên của nhiều nhiệm vụ.

Bạn có thể tạo hai tác vụ bổ sung (hoàn thành sau thời gian chờ đã chỉ định) và sau đó sử dụng WaitAnyđể chờ đợi bất kỳ nhiệm vụ nào hoàn thành trước. Nếu nhiệm vụ hoàn thành đầu tiên là nhiệm vụ "công việc" của bạn, thì bạn đã hoàn thành. Nếu tác vụ hoàn thành trước là nhiệm vụ hết thời gian, thì bạn có thể phản ứng với thời gian chờ (ví dụ: hủy yêu cầu).


1
Tôi đã thấy kỹ thuật này được sử dụng bởi một MVP Tôi thực sự tôn trọng, nó có vẻ sạch sẽ hơn nhiều so với câu trả lời được chấp nhận. Có lẽ một ví dụ sẽ giúp nhận được nhiều phiếu hơn! Tôi tình nguyện làm điều đó trừ khi tôi không có đủ kinh nghiệm Nhiệm vụ để tự tin rằng nó sẽ hữu ích :)
GrahamMc

3
một chủ đề sẽ bị chặn - nhưng nếu bạn ổn với điều đó thì không vấn đề gì. Giải pháp tôi đã thực hiện là giải pháp dưới đây, vì không có chủ đề nào bị chặn. Tôi đọc bài viết trên blog thật sự tốt
JJschk

@JJschk bạn đề cập bạn đã lấy giải pháp below.... đó là cái gì? dựa trên đơn đặt hàng SO?
BozoJoe

và nếu tôi không muốn hủy bỏ nhiệm vụ chậm hơn thì sao? Tôi muốn xử lý nó khi nó kết thúc nhưng trở về từ phương thức hiện tại ..
Akmal Salikhov

18

Những gì như thế này?

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

Bạn có thể sử dụng tùy chọn Task.Wait mà không chặn luồng chính bằng cách sử dụng tác vụ khác.


Trong thực tế trong ví dụ này, bạn không chờ đợi bên trong t1 mà là một nhiệm vụ cao hơn. Tôi sẽ cố gắng làm một ví dụ chi tiết hơn.
as-cii

14

Dưới đây là một ví dụ hoạt động đầy đủ dựa trên câu trả lời được bình chọn hàng đầu, đó là:

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

Ưu điểm chính của việc thực hiện trong câu trả lời này là các tổng quát đã được thêm vào, do đó hàm (hoặc tác vụ) có thể trả về một giá trị. Điều này có nghĩa là bất kỳ chức năng hiện có nào cũng có thể được gói trong chức năng hết thời gian, ví dụ:

Trước:

int x = MyFunc();

Sau:

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

Mã này yêu cầu .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();
            }
        }
    }
}

Hãy cẩn thận

Đã đưa ra câu trả lời này, nói chung không phải là một thực tiễn tốt để có các ngoại lệ được ném vào mã của bạn trong quá trình hoạt động bình thường, trừ khi bạn hoàn toàn phải:

  • Mỗi lần ném ngoại lệ, đó là một hoạt động cực kỳ nặng nề,
  • Các ngoại lệ có thể làm chậm mã của bạn theo hệ số từ 100 trở lên nếu các ngoại lệ nằm trong một vòng lặp chặt chẽ.

Chỉ sử dụng mã này nếu bạn hoàn toàn không thể thay đổi chức năng bạn đang gọi để nó hết thời gian sau khi cụ thể TimeSpan.

Câu trả lời này thực sự chỉ có thể áp dụng khi giao dịch với các thư viện thư viện của bên thứ 3 mà bạn đơn giản không thể cấu trúc lại để bao gồm một tham số hết thời gian.

Cách viết mã mạnh

Nếu bạn muốn viết mã mạnh, quy tắc chung là:

Mỗi hoạt động có khả năng chặn vô thời hạn, phải có thời gian chờ.

Nếu bạn không tuân thủ quy tắc này, mã của bạn cuối cùng sẽ gặp một hoạt động không thành công vì một số lý do, sau đó nó sẽ chặn vô thời hạn và ứng dụng của bạn đã bị treo vĩnh viễn.

Nếu có thời gian chờ hợp lý sau một thời gian, thì ứng dụng của bạn sẽ bị treo trong một khoảng thời gian cực lớn (ví dụ 30 giây), sau đó nó sẽ hiển thị lỗi và tiếp tục theo cách vui vẻ hoặc thử lại.


11

Sử dụng thư viện AsyncEx tuyệt vời của Stephen Cleary , bạn có thể làm:

TimeSpan timeout = TimeSpan.FromSeconds(10);

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

TaskCanceledException sẽ được ném trong trường hợp hết thời gian.


10

Đây là một phiên bản nâng cao một chút của các câu trả lời trước.

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

Sử dụng

InnerCallAsynccó thể mất một thời gian dài để hoàn thành. CallAsynckết thúc nó với một thời gian chờ.

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
Cảm ơn giải pháp! Có vẻ như bạn cần phải vượt qua timeoutCancellationvào delayTask. Hiện tại, nếu bạn hủy bỏ hủy, CancelAfterAsynccó thể ném TimeoutExceptionthay vì TaskCanceledException, nguyên nhân delayTaskcó thể kết thúc trước.
AxelUser

@AxelUser, bạn nói đúng. Tôi đã mất một giờ với một loạt các bài kiểm tra đơn vị để hiểu điều gì đang xảy ra :) Tôi giả sử rằng khi cả hai tác vụ được đưa ra WhenAnyđều bị hủy bởi cùng một mã thông báo, WhenAnysẽ trả về tác vụ đầu tiên. Giả định đó là sai. Tôi đã chỉnh sửa câu trả lời. Cảm ơn!
Josef Bláha

Tôi đang gặp khó khăn trong việc tìm ra cách thực sự gọi nó bằng chức năng <someResult> được xác định; bất kỳ cơ hội nào bạn có thể giải quyết một ví dụ về cách gọi nó?
jhaagsma

1
@jhaagsma, ví dụ thêm!
Josef Bláha

@ JosefBláha Cảm ơn rất nhiều! Tôi vẫn chậm rãi quấn đầu theo cú pháp kiểu lambda, điều đó sẽ không xảy ra với tôi - rằng mã thông báo được chuyển đến tác vụ trong phần thân của CancAfterAsync, bằng cách chuyển vào hàm lambda. Tiện lợi!
jhaagsma

8

Sử dụng Timer để xử lý tin nhắn và tự động hủy. Khi Nhiệm vụ hoàn thành, hãy gọi Vứt bỏ trên bộ hẹn giờ để chúng không bao giờ bắn. Đây là một ví dụ; thay đổi taskDelay thành 500, 1500 hoặc 2500 để xem các trường hợp khác nhau:

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

Ngoài ra, Async CTP cung cấp phương thức TaskEx.Delay sẽ bao bọc bộ định thời trong các tác vụ cho bạn. Điều này có thể cung cấp cho bạn nhiều quyền kiểm soát hơn để thực hiện những việc như đặt TaskScheduler để tiếp tục khi Timer kích hoạt.

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

Anh ta không muốn chủ đề hiện tại bị chặn, nghĩa là không task.Wait().
Cheng Chen

@Danny: Đó chỉ là để làm cho ví dụ hoàn thành. Sau khi Tiếp tục, bạn có thể quay lại và để tác vụ chạy. Tôi sẽ cập nhật câu trả lời của tôi để làm cho nó rõ ràng hơn.
Quartermeister

2
@dtb: Điều gì sẽ xảy ra nếu bạn thực hiện t1 một Nhiệm vụ <Nhiệm vụ <Kết quả >> và sau đó gọi TaskExtensions.Unwrap? Bạn có thể trả lại t2 từ lambda bên trong của bạn và bạn có thể thêm các phần tiếp theo vào tác vụ chưa được thực hiện sau đó.
Quartermeister

Tuyệt vời! Điều đó hoàn toàn giải quyết vấn đề của tôi. Cảm ơn! Tôi nghĩ rằng tôi sẽ đi với giải pháp được đề xuất bởi @ AS-CII, mặc dù tôi ước tôi có thể chấp nhận câu trả lời của bạn cũng như đề xuất TaskExtensions.Unwrap Tôi có mở một câu hỏi mới để bạn có thể nhận được đại diện mà bạn xứng đáng không?
dtb

6

Một cách khác để giải quyết vấn đề này là sử dụng phần mở rộng Reactive:

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

Kiểm tra ở trên bằng cách sử dụng mã dưới đây trong bài kiểm tra đơn vị của bạn, nó hoạt động với tôi

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

Bạn có thể cần không gian tên sau:

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

Một phiên bản chung của câu trả lời của @ Kevan ở trên, sử dụng Tiện ích mở rộng phản ứng.

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

Với Bộ lập lịch tùy chọn:

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: Khi Hết thời gian xảy ra, một ngoại lệ hết thời gian sẽ được đưa ra


0

Nếu bạn sử dụng BlockingCollection để lên lịch tác vụ, nhà sản xuất có thể chạy tác vụ có khả năng chạy dài và người tiêu dùng có thể sử dụng phương thức TryTake có thời gian chờ và mã thông báo hủy được tích hợp.


Tôi sẽ phải viết một cái gì đó lên (không muốn đặt mã độc quyền ở đây) nhưng kịch bản là như thế này. Nhà sản xuất sẽ là mã thực thi phương thức có thể hết thời gian và sẽ đưa kết quả vào hàng đợi khi hoàn tất. Người tiêu dùng sẽ gọi trytake () với thời gian chờ và sẽ nhận được mã thông báo khi hết thời gian. Cả nhà sản xuất và người tiêu dùng sẽ là các tác vụ backround và hiển thị thông báo cho người dùng bằng cách sử dụng bộ xử lý luồng UI nếu cần.
kns98

0

Tôi cảm thấy Task.Delay()nhiệm vụ và CancellationTokenSourcetrong các câu trả lời khác một chút cho trường hợp sử dụng của tôi trong một vòng kết nối mạng chặt chẽ.

Và mặc dù Joe Hoag đang tạo ra một nhiệm vụ. Thời gian sau khi phương pháp trên blog MSDN được truyền cảm hứng, tôi hơi mệt mỏi khi sử dụng TimeoutExceptionđể kiểm soát dòng chảy vì lý do tương tự như trên, vì thời gian chờ được mong đợi thường xuyên hơn không.

Vì vậy, tôi đã thực hiện điều này, cũng xử lý các tối ưu hóa được đề cập trong 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;
    }
}

Một trường hợp sử dụng ví dụ là như sau:

var receivingTask = conn.ReceiveAsync(ct);

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

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

0

Một vài biến thể của câu trả lời của Andrew Arnott:

  1. Nếu bạn muốn đợi một nhiệm vụ hiện có và tìm hiểu xem nó đã hoàn thành hay hết thời gian, nhưng không muốn hủy nó nếu thời gian chờ xảy ra:

    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. Nếu bạn muốn bắt đầu một nhiệm vụ công việc và hủy công việc nếu thời gian chờ xảy ra:

    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. Nếu bạn đã tạo một tác vụ mà bạn muốn hủy nếu thời gian chờ xảy ra:

    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
    }

Một nhận xét khác, các phiên bản này sẽ hủy bộ hẹn giờ nếu thời gian chờ không xảy ra, do đó, nhiều cuộc gọi sẽ không khiến bộ định thời chồng chất.

sjb


0

Tôi đang kết hợp lại ý tưởng của một số câu trả lời khác ở đây và câu trả lời này trên một chủ đề khác thành phương pháp mở rộng kiểu thử. Điều này có lợi nếu bạn muốn một phương thức mở rộng, nhưng tránh một ngoại lệ khi hết thời gian.

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

Chắc chắn không làm điều này, nhưng đó là một lựa chọn nếu ... tôi không thể nghĩ ra một lý do hợp lệ.

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.