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.