问题
I'm trying to do the equivalent of the dollowing C# 5 pseudocode:-
async Task<int> CallAndTranslate()
{
try
{
return await client.CallAsync();
} catch(FaultException ex) {
if (ex.FaultCode ...)
throw new Exception("translated");
}
}
Given an arbitrary Task which does not return a result, translating exceptions when backporting from C# 5 is easy using the technique supplied by @Drew Marsh
This technique doesn't generalize trivially to Task<T>
as any overload of Task.ContinueWith
I can see returns a bald Task
, not a Task<T>
.
Is there a way to achieve this using the TPL APIs without having to resort to:
- wrapping it in another
Task<T>
- causing the exception to go through the machinations of getting thrown and caught through the exception handling mechanisms
- ADDED After intial answer.... should leave stack trace alone if exception is not to be translated
Here's my naive placeholder implementation:
public class TranslatingExceptions
{
Task<int> ApiAsync()
{
return Task<int>.Factory.StartNew( () => {
throw new Exception( "Argument Null" ); } );
}
public Task<int> WrapsApiAsync()
{
return ApiAsync().TranslateExceptions(x=>{
if (x.Message == "Argument Null" )
throw new ArgumentNullException();
});
}
[Fact]
public void Works()
{
var exception = Record.Exception( () =>
WrapsApiAsync().Wait() );
Assert.IsType<ArgumentNullException>( exception.InnerException );
}
}
The following Task<T>
extension implements my placeholder implementation:
static class TaskExtensions
{
public static Task<T> TranslateExceptions<T>( this Task<T> task, Action<Exception> translator )
{
// TODO REPLACE NAIVE IMPLEMENTATION HERE
return Task<T>.Factory.StartNew( () =>
{
try
{
return task.Result;
}
catch ( AggregateException exception )
{
translator( exception.InnerException );
throw;
}
} );
}
}
回答1:
You can imitate await
in .NET 4.0 using iterators ( yield
), but it's not pretty.
Without a state machine, you're missing the whole point of await
which is to return control to the caller until the work is complete and only then continue execution.
Um, maybe I've missed the point! If you just want to use a ContinueWith<T>
it's just a slight tweak to the code from Drew Marsh:
public Task<int> ApiAsync() // The inner layer exposes it exactly this way
{
return Task<int>.Factory.StartNew( () =>
{ throw new Exception( "Argument Null" ); } );
}
// this layer needs to expose it exactly this way
public Task<int> WrapsApiAsync()
{
// Grab the task that performs the "original" work
Task<int> apiAsyncTask = ApiAsync();
// Hook a continuation to that task that will do the exception "translation"
Task<int> result = apiAsyncTask.ContinueWith( antecedent =>
{
// Check if the antecedent faulted
// If so check what the exception's message was
if ( antecedent.IsFaulted )
{
if ( antecedent.Exception.InnerException.Message == "Argument Null" )
{
throw new ArgumentNullException();
}
throw antecedent.Exception.InnerException;
}
return antecedent.Result;
},
TaskContinuationOptions.ExecuteSynchronously );
// Now we return the continuation Task from the wrapper method
// so that the caller of the wrapper method waits on that
return result;
}
UPDATE: sample using TaskCompletionSource
public static Task<int> WrapsApiAsync()
{
var tcs = new TaskCompletionSource<int>();
Task<int> apiAsyncTask = ApiAsync();
apiAsyncTask.ContinueWith( t =>
{
switch ( t.Status )
{
case TaskStatus.RanToCompletion:
tcs.SetResult( task.Result );
break;
case TaskStatus.Canceled:
tcs.SetCanceled();
break;
case TaskStatus.Faulted:
if ( t.Exception.InnerException.Message == "Argument Null" )
{
try
{
throw new ArgumentNullException();
}
catch ( ArgumentNullException x )
{
tcs.SetException( x );
}
}
else
{
tcs.SetException( t.Exception.InnerException );
}
break;
}
}
);
return tcs.Task;
}
来源:https://stackoverflow.com/questions/8882211/translating-exceptions-for-taskt-with-task-parallel-library-a-la-await