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.