مدیریت خطا در برنامهنویسی ناهمگام #C: درک Task.FromException
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۳/۰۷ ۰۸:۳۹
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
try-catch برای مدیریت خطاها عادت دارند، متد Task.FromException رویکردی قدرتمند برای مدیریت استثناها در الگوی ناهمگام مبتنی بر تسک (Task-based Asynchronous Pattern - TAP) ارائه میدهد. این مقاله به بررسی عمیق Task.FromException، کاربردهای آن، الگوهای پیشرفته، بهترین شیوهها و یک مثال کاربردی در دنیای واقعی میپردازد.Task.FromExceptionTask.FromException یک متد استاتیک است که یک شیء Task را ایجاد میکند که در وضعیت خطا (Faulted State) به پایان رسیده است و شامل استثنای مشخصشده است. این متد بخشی از کتابخانه موازی تسک (Task Parallel Library - TPL) است و راهی صریح برای ایجاد یک تسک خطاخورده فراهم میکند. دو نسخه از این متد وجود دارد:public static Task FromException(Exception exception): برای تسکهایی که نتیجهای باز نمیگردانند.public static Task<TResult> FromException<TResult>(Exception exception): برای تسکهایی که یک نتیجه از نوع TResult را باز میگردانند.Task.FromException برای تبدیل یک استثنای گرفته شده به یک تسک خطاخورده استفاده میشود. به عنوان مثال، در یک متد async، اگر عملیاتی ناموفق باشد، میتوان به جای پرتاب مستقیم استثنا، یک تسک خطاخورده را بازگرداند:public async Task ProcessDataAsync()
{
try
{
// عملیات ناهمگام
await DoWorkAsync();
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}Task.FromException استفاده کرد:public async Task<string> GetDataAsync()
{
try
{
return await FetchDataAsync();
}
catch (Exception ex)
{
return Task.FromException<string>(ex);
}
}Task.FromExceptionTask.FromException در سناریوهای مختلفی که نیاز به مدیریت صریح خطا در تسکهای ناهمگام است، بسیار مفید است.Task.FromException برای بازگرداندن سریع یک تسک خطاخورده با استثنای اعتبارسنجی مناسب استفاده کرد. این کار از ادامه اجرای کد با دادههای نامعتبر جلوگیری میکند:public async Task<ValidationResult> ValidateDataAsync(DataModel data)
{
if (data == null)
{
return await Task.FromException<ValidationResult>(
new ArgumentNullException(nameof(data)));
}
if (!IsValid(data))
{
return await Task.FromException<ValidationResult>(
new ValidationException("Invalid data format"));
}
return await PerformValidationAsync(data);
}Task.FromException یک روش تمیز را فراهم میکند. این رویکرد به ویژه در لایههای مختلف یک برنامه مفید است که هر لایه مسئول مدیریت خطاهای خاص خود است اما در نهایت خطا را برای مدیریت نهایی به لایههای بالاتر منتقل میکند:public async Task<Result> ProcessWithErrorHandlingAsync()
{
try
{
var data = await LoadDataAsync();
return await ProcessDataAsync(data);
}
catch (DataLoadException ex)
{
// _logger.LogError(ex, "Failed to load data"); // فرض بر وجود لاگر
return await Task.FromException<Result>(ex);
}
catch (ProcessingException ex)
{
// _logger.LogError(ex, "Failed to process data"); // فرض بر وجود لاگر
return await Task.FromException<Result>(ex);
}
}Task.FromException به شما امکان میدهد تا این استثناها را به طور صریح در یک تسک خطاخورده کپسوله کنید:public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
public async Task<BusinessResult> ProcessBusinessLogicAsync()
{
if (!IsBusinessRuleValid())
{
return await Task.FromException<BusinessResult>(
new BusinessException("Business rule violation"));
}
return await ExecuteBusinessLogicAsync();
}Task.FromExceptionTask.FromException میتواند در پیادهسازی الگوهای پیچیدهتر مدیریت خطا در سیستمهای توزیعشده و مقاوم نیز به کار رود.AggregateException استفاده کنید:public async Task<Result> ProcessMultipleItemsAsync(IEnumerable<Item> items)
{
var exceptions = new List<Exception>();
foreach (var item in items)
{
try
{
await ProcessItemAsync(item);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
{
return await Task.FromException<Result>(
new AggregateException(exceptions));
}
return new Result();
}Task.FromException میتواند برای بازگرداندن یک استثنای خاص پس از اتمام تمام تلاشها استفاده شود:public async Task<Result> ExecuteWithRetryAsync(
Func<Task<Result>> operation,
int maxRetries)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await operation();
}
catch (Exception ex)
{
if (i == maxRetries - 1)
{
return await Task.FromException<Result>(
new RetryException($"Failed after {maxRetries} attempts", ex));
}
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i))); // تاخیر نمایی
}
}
// این خط نباید قابل دسترسی باشد مگر اینکه مشکلی در منطق وجود داشته باشد
return await Task.FromException<Result>(
new InvalidOperationException("Should not reach here"));
}Task.FromException میتواند به عنوان بخشی از این الگو برای بازگرداندن فوری یک تسک خطاخورده زمانی که مدار باز (Open) است، استفاده شود:public class CircuitBreaker
{
// private readonly ILogger<CircuitBreaker> _logger; // فرض بر وجود لاگر
private bool _isOpen;
public async Task<Result> ExecuteAsync(Func<Task<Result>> operation)
{
if (_isOpen)
{
return await Task.FromException<Result>(
new CircuitBreakerException("Circuit is open"));
}
try
{
return await operation();
}
catch (Exception ex)
{
_isOpen = true;
// _logger.LogError(ex, "Circuit breaker opened"); // فرض بر وجود لاگر
return await Task.FromException<Result>(ex);
}
}
}Task.FromException و حفظ کد قابل نگهداری، رعایت بهترین شیوهها ضروری است.Exception عمومی کمتر مفید است زیرا اطلاعات کافی در مورد ماهیت خطا ارائه نمیدهد:// خوب
return Task.FromException(new ValidationException("Invalid input"));
// بد
return Task.FromException(new Exception("Invalid input"));public async Task<Result> ProcessWithContextAsync(InputData data)
{
try
{
return await ProcessAsync(data);
}
catch (Exception ex)
{
return await Task.FromException<Result>(
new ProcessingException(
$"Failed to process data with ID: {data.Id}",
ex));
}
}public async Task<Result> ProcessWithLoggingAsync()
{
try
{
return await ProcessAsync();
}
catch (Exception ex)
{
// _logger.LogError(ex, "Operation failed"); // فرض بر وجود لاگر
return await Task.FromException<Result>(ex);
}
}Task.FromException در یک سناریوی واقعی، یک سرویس پردازش داده را در نظر بگیرید:public class DataProcessingService
{
// private readonly ILogger<DataProcessingService> _logger; // فرض بر وجود لاگر
private readonly IDataValidator _validator;
private readonly IDataProcessor _processor;
public DataProcessingService(IDataValidator validator, IDataProcessor processor) // سازنده
{
_validator = validator;
_processor = processor;
}
public async Task<ProcessedData> ProcessDataAsync(RawData data)
{
try
{
// اعتبارسنجی ورودی
if (!await _validator.ValidateAsync(data))
{
// _logger.LogWarning("Invalid data received: {@Data}", data); // فرض بر وجود لاگر
return await Task.FromException<ProcessedData>(
new ValidationException("Invalid data format"));
}
// پردازش داده
var result = await _processor.ProcessAsync(data);
// اعتبارسنجی خروجی
if (!IsValid(result)) // فرض بر وجود متد IsValid
{
// _logger.LogError("Invalid result generated: {@Result}", result); // فرض بر وجود لاگر
return await Task.FromException<ProcessedData>(
new ProcessingException("Invalid result generated"));
}
return result;
}
catch (Exception ex)
{
// _logger.LogError(ex, "Failed to process data: {@Data}", data); // فرض بر وجود لاگر
return await Task.FromException<ProcessedData>(ex);
}
}
}DataProcessingService از Task.FromException در چندین نقطه استفاده میکند:ValidationException را در یک تسک خطاخورده بازمیگرداند.ProcessingException را بازمیگرداند.Task.FromException ابزاری قدرتمند برای مدیریت استثناها در کد ناهمگام #C است. این متد راهی تمیز و کارآمد برای انتشار خطاها و حفظ زمینه ناهمگام فراهم میکند. با درک زمانی که باید از آن استفاده کرد و با رعایت بهترین شیوهها مانند استفاده از استثناهای خاص، گنجاندن اطلاعات متنی و ثبت خطاها، توسعهدهندگان میتوانند کدی مقاومتر و قابل نگهداری ایجاد کنند. در حالی که Task.FromException ممکن است در هر عملیات ناهمگام مورد نیاز نباشد، تسلط بر آن میتواند به طور قابل توجهی کیفیت و استحکام برنامههای .NET را بهبود بخشد.