Captured variable is disposed in outer scope - c#

I have the following piece of sample code to which resharper complains "Captured variable is disposed in outer scope". I do not any issues here as ExecuteAsync which calls the unnamed lambda is awaited so httpClient will not be used outside the scope. Is this a false positive?
private static async Task MyTestFunction(IHttpClientFactory httpClientFactory) {
string baseUrl = "http://localhost:8000";
using var httpClient = httpClientFactory.CreateClient();
try {
await ExecuteAsync(
async () => {
try {
await httpClient.GetAsync(new Uri(baseUrl)).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex);
throw;
}
}).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw;
}
}
private static async Task ExecuteAsync(Func<Task> func) {
await func().ConfigureAwait(false);
}

It's complaining that httpClient won't be disposed until ExecuteAsync finished because it's declared early. It could be disposed earlier if you declare it within the inner try statement.
Note: this will probably have very little performance benefits if any, ReSharper sticks to a rigid set of rules for things that it flags.
It's also a good idea to make your base url a const variable as it never changes.
private const string BaseUrl = "http://localhost:8000";
private static async Task MyTestFunction(IHttpClientFactory httpClientFactory)
{
try {
await ExecuteAsync(
async () => {
try {
using var httpClient = httpClientFactory.CreateClient();
await httpClient.GetAsync(new Uri(BaseUrl)).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex);
throw;
}
}).ConfigureAwait(false);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
throw;
}
}
private static async Task ExecuteAsync(Func<Task> func) {
await func().ConfigureAwait(false);
}

Related

Should DisposeAsync throw background task exceptions, or leave it to the client to observe explicitly?

I don't think this question is a duplicate of "Proper way to deal with exceptions in DisposeAsync".
Let's say my class that implements IAsynsDisposable because it has a long-running background task, and DisposeAsync terminates that task. A familiar pattern might be the Completion property, e.g. ChannelReader<T>.Completion (despite ChannelReader doesn't implement IAsynsDisposable).
Is it considered a good practice to propagate the Completion task's exceptions outside DisposeAsync?
Here is a complete example that can be copied/pasted into a dotnet new console project. Note await this.Completion inside DisposeAsync:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Alternatively, I can observe service.Completion explicitly in the client code (and ignore its exceptions inside DiposeAsync to avoid them being potentially thrown twice), like below:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
await service.Completion;
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
catch
{
// the client should observe this.Completion
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Is there a concensus about which option is better?
For now, I've settled on a reusable helper class LongRunningAsyncDisposable (here's a gist, warning: barely tested yet), which allows:
to start a background task;
stop this task (via a cancellation token) by calling IAsyncDisposable.DisposeAsync at any time, in a thread-safe, concurrency-friendly way;
configure whether DisposeAsync should re-throw the task's exceptions (DisposeAsync will await the task's completion either way, before doing a cleanup);
observe the task's status, result and exceptions at any time via LongRunningAsyncDisposable.Completion property.

Simple general exception handling in async code without boilerplate

We start using CancellationToken in out app a lot, so we have to change exception handling correspondingly:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await DoJob(cts.Token);
Console.WriteLine("Successfully finished");
}
private static async Task DoJob(CancellationToken ct)
{
try
{
await Task.Delay(1000, ct);
}
catch (Exception e) when(!(e is OperationCanceledException))
{
Console.WriteLine("Do cleanup in case of error.");
}
}
}
The idea behind this code is that if someone use catch(Exception e) (please do not blame me for this) and forgot to exclude CancellationToken, an error handling is executed, for example, there is a log that operation failed. But it is not true, id doesn't fail, it just has been canceled. And cancellation should be handled differently then failure.
It seems to me like a big boilerplate to write practically in every general catch
catch (Exception e) when(!(e is OperationCanceledException))
Is there some more robust solution with less boilerplate?
You could create a method that accepts a Func<Task> and catches the exception(s), e.g.:
class Program
{
static async Task Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource(100);
await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
Console.WriteLine("Successfully finished");
}
private static async Task GeneralDoJobAndCatchException(Func<Task> func)
{
try
{
await func();
}
catch (OperationCanceledException) { }
catch (Exception e)
{
Console.WriteLine("Do error handling");
}
}
private static async Task DoJob(CancellationToken ct)
{
await Task.Delay(1000, ct);
}
}
We're having the exact same problem. Mainly there is a while-loop that checks for the CancellationToken but you've to catch this exception.
We created the following extension method:
public static async Task<TaskStatus> HideCancellationException(this Task task)
{
try
{
await task;
return task.Status;
}
catch (OperationCanceledException)
{
return TaskStatus.Canceled;
}
}
Having this extension method allows to change this code:
while (!cancellationToken.IsCancellationRequested)
{
// do stuff here...
try
{
await Task.Delay(..., cancellationToken);
}
catch (OperationCanceledException)
{
// expected
}
}
to something like that:
while (!cancellationToken.IsCancellationRequested)
{
// Do stuff here.
await Task.Delay(..., cancellationToken).HideCancellationException();
}
Keep in mind that there is explicitly no overload for Task<T> because the return value in case of cancellation is default. You can't distinguish between default as normal task result and default as result of cancellation. In that case it's better to catch the exception.
You could get rid of the try-catch block altogether by awaiting indirectly with Task.WhenAny, and then querying the status of the completed task:
private static async Task DoJob(CancellationToken ct)
{
var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
if (completedTask.IsFaulted)
{
Console.WriteLine("Error: " + completedTask.Exception.InnerException);
}
else if (completedTask.IsCanceled)
{
// Do nothing
}
else // Success
{
// Do nothing
}
}

Handling exception in task

I'm new to TPL.
I need to handle exception when the SendEmailAlert() method throws any error.Is the following code correct please?
public Task MyMethod()
{
DoSomething();
try
{
string emailBody = "TestBody";
string emailSubject = "TestSubject";
Task.Run(()=> SendEmailAlert(arrEmailInfo));
}
catch (AggregateException ex)
{
ex.Handle((e) =>
{
log.Error("Error occured while sending email...", e);
return true;
}
);
}
}
private void SendEmailAlert(string[] arrEmailInfo)
{
MyClassX.SendAlert(arrEmailnfo[0], arrEmailnfo[1]);
}
I forced an error from within SendEmailAlert() method.But the exception is not getting caught. Could someone advise?
Thanks.
Your Task.Run runs in a different context (you would need a try/catch inside it; or check if the task is done). You could change to use async/await.
Example:
public async void MyMethod()
{
try
{
await ExceptionMethod();
}
catch (Exception ex)
{
// got it
}
}
public async Task ExceptionMethod()
{
throw new Exception();
}

Xamarin: Exceptions raised from tasks are not propagated

I have the following code in Xamarin (tested in ios):
private static async Task<string> TaskWithException()
{
return await Task.Factory.StartNew (() => {
throw new Exception ("Booo!");
return "";
});
}
public static async Task<string> RunTask()
{
try
{
return await TaskWithException ();
}
catch(Exception ex)
{
Console.WriteLine (ex.ToString());
throw;
}
}
Invoking this as await RunTask(), does throw the exception from the TaskWithException method, but the catch method in RunTask is never hit. Why is that? I would expect the catch to work just like in Microsoft's implementation of async/await. Am I missing something?
You cant await a method inside of a constructor, so thats why you can't catch the Exception.
To catch the Exception you must await the operation.
I have here two ways of calling an async method from the constructor:
1. ContinueWith solution
RunTask().ContinueWith((result) =>
{
if (result.IsFaulted)
{
var exp = result.Exception;
}
});
2. Xamarin Forms
Device.BeginInvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});
3. iOS
InvokeOnMainThread(async () =>
{
try
{
await RunTask();
}
catch (Exception ex)
{
Console.WriteLine (ex.ToString());
}
});

Exception not caught in Task.Run wrapped method

New to async await integration in C# 5. I'm working with some basic Task based methods to explore async await and the TPL. In this example below I'm calling a web service with a timeout of 5 seconds. If the timeout expires it should throw an exception so I can return false from the method. However, the timeout never occurs, or maybe it does but the Task never returns.
public static Task<bool> IsConnectedAsync()
{
return Task.Run(() =>
{
try
{
using (WSAppService.AppService svc = new NCSoftware.Common.WSAppService.AppService(GetServiceUrl(WebService.app)){Timeout = 5000})
{
return svc.PingB();
}
}
catch (Exception ex)
{
Logger.LogException(ex.Message, ex, "IsConnectedAsync");
}
return false;
});
}
If you could please help with how to properly handle this so that if the timeout occurs or even better, an exception occurs, the Task does return.
In general, you shouldn't use Task.Run if you're wrapping async services. Since this is a service reference, you should be able to expose an async method (returning Task) directly from the service, in which case you could use:
public async static Task<bool> IsConnectedAsync()
{
try
{
using (WSAppService.AppService svc = new NCSoftware.Common.WSAppService.AppService(GetServiceUrl(WebService.app)){Timeout = 5000})
{
return await svc.PingBAsync();
}
}
catch (Exception ex)
{
Logger.LogException(ex.Message, ex, "IsConnectedAsync");
}
return false;
}
If you must wrap via Task.Run (again, this is not suggested, as it's turning synchronous code into async via the thread pool, which is typically better handled by the user at the top level), you could do:
public async static Task<bool> IsConnectedAsync()
{
try
{
return await Task.Run(() =>
{
using (WSAppService.AppService svc = new NCSoftware.Common.WSAppService.AppService(GetServiceUrl(WebService.app)){Timeout = 5000})
{
return svc.PingB();
}
}
}
catch (Exception ex)
{
Logger.LogException(ex.Message, ex, "IsConnectedAsync");
return false;
}
}

Categories