Tất cả các cuộc gọi dịch vụ trong ứng dụng của tôi được thực hiện dưới dạng tác vụ.Khi có công việc bị lỗi, tôi cần trình bày cho người dùng một hộp thoại để thử lại hoạt động cuối cùng thất bại.Nếu người dùng chọn thử lại chương trình sẽ thử lại tác vụ, nếu không thì việc thực thi chương trình sẽ tiếp tục sau khi ghi ngoại lệ. Bất kỳ ai có ý tưởng cấp cao về cách triển khai chức năng này?Thử lại tác vụ nhiều lần dựa trên đầu vào của người dùng trong trường hợp ngoại lệ trong tác vụ
Trả lời
CẬP NHẬT 5/2017
C# 6 bộ lọc ngoại lệ làm cho catch
khoản đơn giản hơn rất nhiều:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
}
}
và phiên bản đệ quy:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch when (retryCount-- > 0){}
return await Retry(func, retryCount);
}
ORIGINAL
Có nhiều cách để mã chức năng Thử lại: bạn có thể sử dụng tính năng đệ quy hoặc lặp lại tác vụ. Đã có một discussion trong nhóm Người dùng .NET của Hy Lạp trong khi quay lại các cách khác nhau để thực hiện chính xác điều này.
Nếu bạn đang sử dụng F #, bạn cũng có thể sử dụng cấu trúc Async. Thật không may, bạn không thể sử dụng các cấu trúc async/await ít nhất trong Async CTP, bởi vì mã được tạo ra bởi trình biên dịch không giống như nhiều chờ đợi hoặc có thể rethrows trong các khối catch.
Phiên bản đệ quy có lẽ là cách đơn giản nhất để xây dựng Thử lại trong C#.Các phiên bản sau không sử dụng Unwrap và cho biết thêm một sự chậm trễ bắt buộc trước khi thử lại:
private static Task<T> Retry<T>(Func<T> func, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
Task.Factory.StartNewDelayed(delay).ContinueWith(t =>
{
Retry(func, retryCount - 1, delay,tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
Chức năng StartNewDelayed xuất phát từ ParallelExtensionsExtras mẫu và sử dụng một bộ đếm thời gian để kích hoạt một TaskCompletionSource khi timeout xảy ra.
Chiếc F # phiên bản đơn giản hơn rất nhiều:
let retry (asyncComputation : Async<'T>) (retryCount : int) : Async<'T> =
let rec retry' retryCount =
async {
try
let! result = asyncComputation
return result
with exn ->
if retryCount = 0 then
return raise exn
else
return! retry' (retryCount - 1)
}
retry' retryCount
Unfortunatley, nó là không thể để viết một cái gì đó tương tự như trong C# sử dụng async/chờ đợi từ Async CTP vì trình biên dịch không thích chờ đợi báo cáo bên trong một khối catch. Nỗ lực sau đây cũng không silenty, bởi vì thời gian chạy không thích gặp phải một chờ đợi sau một ngoại lệ:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await TaskEx.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
Đối với yêu cầu người dùng, bạn có thể sửa đổi Thử lại để gọi một chức năng mà yêu cầu người dùng và trả về một nhiệm vụ thông qua một TaskCompletionSource để kích hoạt các bước tiếp theo khi các câu trả lời của người dùng, ví dụ:
private static Task<bool> AskUser()
{
var tcs = new TaskCompletionSource<bool>();
Task.Factory.StartNew(() =>
{
Console.WriteLine(@"Error Occured, continue? Y\N");
var response = Console.ReadKey();
tcs.SetResult(response.KeyChar=='y');
});
return tcs.Task;
}
private static Task<T> RetryAsk<T>(Func<T> func, int retryCount, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
tcs = new TaskCompletionSource<T>();
Task.Factory.StartNew(func).ContinueWith(_original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
tcs.SetException(_original.Exception.InnerExceptions);
else
AskUser().ContinueWith(t =>
{
if (t.Result)
RetryAsk(func, retryCount - 1, tcs);
});
}
else
tcs.SetResult(_original.Result);
});
return tcs.Task;
}
với tất cả các continuations, bạn có thể thấy tại sao một phiên bản async của Thử lại rất đáng mơ ước.
UPDATE:
Trong Visual Studio 2012 Beta hai phiên bản sau làm việc:
Một phiên bản với một vòng lặp while:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
while (true)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
retryCount--;
}
}
}
và một phiên bản đệ quy:
private static async Task<T> Retry<T>(Func<T> func, int retryCount)
{
try
{
var result = await Task.Run(func);
return result;
}
catch
{
if (retryCount == 0)
throw;
}
return await Retry(func, --retryCount);
}
Cảm ơn bạn đã giải thích chi tiết, hãy thử cách này và cho bạn biết –
+1 Làm tốt lắm. [Đã thêm một đoạn dưới đây] (http://stackoverflow.com/a/16354355/11635) –
Khi ở mức cao, tôi thấy nó giúp tạo chữ ký chức năng từ những gì bạn có và những gì bạn muốn.
Bạn có:
- Một chức năng cung cấp cho bạn một nhiệm vụ (
Func<Task>
). Chúng tôi sẽ sử dụng chức năng vì bản thân các tác vụ không thể thử lại được. - Một chức năng xác định nếu nhiệm vụ tổng thể được hoàn thành hoặc sẽ được thử lại (
Func<Task, bool>
)
Bạn muốn:
- một nhiệm vụ chung
Vì vậy, bạn sẽ có một chức năng như:
Task Retry(Func<Task> action, Func<Task, bool> shouldRetry);
Mở rộng thực hành bên trong hàm, nhiệm vụ khá nhiều có 2 thao tác để thực hiện với chúng, đọc trạng thái của chúng và ContinueWith
. Để thực hiện các nhiệm vụ của riêng bạn, TaskCompletionSource
là điểm khởi đầu tốt. Lần thử đầu tiên có thể trông giống như sau:
//error checking
var result = new TaskCompletionSource<object>();
action().ContinueWith((t) =>
{
if (shouldRetry(t))
action();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and similar for Canceled and RunToCompletion
}
});
Vấn đề rõ ràng ở đây là chỉ có 1 lần thử lại sẽ xảy ra. Để giải quyết vấn đề đó, bạn cần phải thực hiện một cách để hàm gọi chính nó. Cách thông thường để làm điều này với lambdas là một cái gì đó như thế này:
//error checking
var result = new TaskCompletionSource<object>();
Func<Task, Task> retryRec = null; //declare, then assign
retryRec = (t) => { if (shouldRetry(t))
return action().ContinueWith(retryRec).Unwrap();
else
{
if (t.IsFaulted)
result.TrySetException(t.Exception);
//and so on
return result.Task; //need to return something
}
};
action().ContinueWith(retryRec);
return result.Task;
Cảm ơn, sẽ hãy thử và cho bạn biết –
Đây là phiên bản được riffed của Panagiotis Kanavos's excellent answer mà tôi đã thử nghiệm và đang sử dụng trong sản xuất.
Nó đề cập đến một số điều mà rất quan trọng với tôi:
- muốn để có thể quyết định có nên thử lại dựa trên số trước nỗ lực và ngoại lệ từ nỗ lực hiện tại
- Không muốn dựa vào
async
(hạn chế ít môi trường) - Muốn có kết quả
Exception
trong trường hợp thất bại bao gồm chi tiết từ mỗi nỗ lực
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry)
{
return RetryWhile<T>(func, shouldRetry, new TaskCompletionSource<T>(), 0, Enumerable.Empty<Exception>());
}
static Task<T> RetryWhile<T>(
Func<int, Task<T>> func,
Func<Exception, int, bool> shouldRetry,
TaskCompletionSource<T> tcs,
int previousAttempts, IEnumerable<Exception> previousExceptions)
{
func(previousAttempts).ContinueWith(antecedent =>
{
if (antecedent.IsFaulted)
{
var antecedentException = antecedent.Exception;
var allSoFar = previousExceptions
.Concat(antecedentException.Flatten().InnerExceptions);
if (shouldRetry(antecedentException, previousAttempts))
RetryWhile(func,shouldRetry,previousAttempts+1, tcs, allSoFar);
else
tcs.SetException(allLoggedExceptions);
}
else
tcs.SetResult(antecedent.Result);
}, TaskContinuationOptions.ExecuteSynchronously);
return tcs.Task;
}
- 1. Lập lịch biểu tác vụ bằng django-celery dựa trên đầu vào của người dùng
- 2. Đầu vào tác vụ so với các nguồn tác vụ
- 3. Rx và tác vụ - hủy tác vụ đang chạy khi tác vụ mới được sinh ra?
- 4. Tác vụ lặp lại của Android
- 5. Chạy tác vụ trong nhiều chuỗi trên node.js
- 6. Thử lại khung công tác
- 7. Lập lịch tác vụ trong Spring/Java
- 8. Hủy tác vụ đang ném một ngoại lệ
- 9. Tác vụ VoiceOver tùy chỉnh trong UITableViewCell
- 10. Tác vụ phụ LongRunning tác vụ?
- 11. Cách thực hiện tác vụ trên OK của JOptionPane.showMessageDialog
- 12. Khởi động lại tác vụ hoặc tạo một tác vụ mới?
- 13. Phản ứng với tác vụ người dùng trong AudioPlayerKhi ứng dụng bị tạm ngưng
- 14. Tương tác của người dùng trên UIImageView
- 15. Thêm thanh tác vụ vào ListActivity
- 16. Quay lại đầu ra từ tác vụ MsBuild?
- 17. Chỉ định nhiều người dùng cho một tác vụ quy trình làm việc trong sharepoint
- 18. Có thay thế dựa trên Tác vụ cho System.Threading.Timer không?
- 19. Trường hợp sử dụng cho tác vụ buildNeeded?
- 20. Mô phỏng tác vụ nền trên AppHarbor
- 21. Ẩn Thanh tác vụ trong khi hiển thị Thanh tác vụ Split
- 22. Tôi có thể dừng Visual Studio trên các ngoại lệ chưa được xử lý bên trong Mã tác vụ không?
- 23. Tôi có thể ghi đè tác vụ: môi trường trong test_helper.rb để kiểm tra tác vụ cào không?
- 24. Hợp tác đa tác vụ bằng cách sử dụng TPL
- 25. Tác vụ của diễn viên trong libgdx
- 26. robotium - nhấp vào tab thanh tác vụ
- 27. Tác vụ và tác vụ.WaitAll với xử lý ngoại lệ hết giờ
- 28. Tác vụ trì hoãn: lập lịch thực hiện lần đầu tiên trong Spring 3
- 29. mục menu trên thanh tác vụ onclick?
- 30. Cách cập nhật giao diện người dùng từ các tác vụ con trong WinForms
@svick tôi đã không cố gắng để thực hiện chức năng này đó là một nhiệm vụ tương lai sắp tới, trông giống như chúng tôi đã nhận những ý tưởng thú vị đã có, đã đi qua chúng một cách chi tiết và cung cấp cho nó một thử –