2016-04-13 13 views
0

`に依存:例外動作は、私は次のシナリオを有するasync`キーワード

成分Aは、インタフェースIMyInterfaceとその実装を登録する他の成分のための登録メカニズムを提供します。私は「並列に」のすべての実装のための方法を上開始したいコンポーネントAでは

public interface IMyInterface 
{ 
    Task DoSomethingAsync(Context context); 
} 

して、タスクを待つ:

インタフェースは次のようになります。 実装者は実装に例外をスローすることがあります(具体的なシナリオではIOが関与し、IOExceptionが発生することが予想され、コンポーネントAは例外を正しくキャッチします)。

コードはだからここに、以下の

var tasks = new List<Task>(); 

foreach (var impl in implementors) 
{ 
    var context = ...; 
    tasks.Add(impl.DoSomethingAsync(context)); 
} 

// now do something different that takes some time 

try 
{ 
    await Task.WhenAll(tasks); 
} 
catch(Exception e) 
{ 
    // swallow. we handle the exceptions for each task below. 
} 

foreach (var task in tasks) 
{ 
    if (task.IsFaulted) 
     // log, recover, etc... 
} 

のように見える私の問題である: 私は、単一のタスクのためのawaitを使用してないておりますので、例外動作が実装者は、「作成」の方法に依存返されたTask

public class Implementor1 : IMyInterface 
{ 
    public async Task DoSomethingAsync(Context context) 
    { 
     // no awaits used in code here! 
     throw new Exception("oh the humanity"); 
    } 
} 

public class Implementor2 : IMyInterface 
{ 
    public Task DoSomethingAsync(Context context) 
    { 
     throw new Exception("oh the humanity"); 
     return Task.CompletedTask; 
    } 
} 

(違いに注意してください。最初の実装は、asyncキーワードを使用)

インプリ1にDoSomethingAsyncを呼び出す場合、例外がタスク・オブジェクトが「故障」に設定されている成分Aでスローされていないとタスクから例外を取得できます。 - >私が期待した通り。

実装者2でDoSomethingAsyncを呼び出すと、すぐに例外がスローされます。 - >ではない私が欲しいもの。

ここに質問があります: どのようにしてこのような状況で常に行動1を観察できますか?私は、他のコンポーネントの作者が私のインターフェースをどのように実装するかを制御することはできません。

答えて

1

AFAIUあなたが「できるあなただけ書くことができます.NET 4.6を使用して

インターフェイスの実装を変更しても、一貫した例外処理戦略を持ちたいと思っています。

もう1つの非同期メソッドで呼び出しをラップすることができます。

private async Task DoSomethingAsync(IMyInterface instance, Context context) 
{ 
    await instance.DoSomethingAsync(context); 
} 

そして

foreach (var impl in implementors) 
{ 
    var context = ...; 
    tasks.Add(DoSomethingAsync(impl, context)); 
} 

として、この方法でそれを呼び出し、例外がTaskで包み、非同期メソッドによって返される代わりに、すぐに伝播されません。

Decoratorを作成し、そこにasyncというメソッドを実装すると、さらにうまくできます。次に、メソッドを呼び出す前に、他のすべての実装をデコレータで飾ります。

一方、実装を変更できる場合は、servyの答えが必要です。

+0

賢いアイデア、私はこの1つが好きです! 私はちょうど1つの行にこれを分解しました: 'tasks.Add(Task.Run(async()=> await impl.DoSomethingAsync(context))); ' –

+0

@ cpt.jazz:私はないと思います'Task.Run'はここでは適切です。実行コンテキストをスレッドプールに変更して、不必要なプレッシャーをかけています。 –

+0

@Stephen:別の方法を避けたいときは、どうすればこれを改善できますか? –

0

Taskを返す場合は、メソッド自体に例外がスローされるのではなく、Taskを作成し、フォールトとしてマークして戻す必要があります。

return Task.FromException(new Exception("oh the humanity")); 

を使用すると、.NETの以前のバージョンを使用している場合は、以下のFromException実装を使用することができます:

public static Task FromException(Exception e) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 
    tcs.SetException(e); 
    return tcs.Task; 
} 
public static Task<T> FromException<T>(Exception e) 
{ 
    var tcs = new TaskCompletionSource<T>(); 
    tcs.SetException(e); 
    return tcs.Task; 
} 
+0

はい、私が言ったように、私は他のコンポーネントの作者が自分のインターフェースを実装する方法を制御できません。 'await'を使わない場合、コンパイラは' async'キーワードを削除することを提案します。実装はIOを行う可能性が最も高いため、著者がIOExceptionを 'Task'にラップするのを忘れた場合はどうなりますか?どのようにしてこのような状況を処理する_robust_コードを書くことができますか? –

+0

@ cpt.jazz例外を捕捉し、その例外を表すタスクを返します。明らかにメソッドを変更することはできません。メソッドを変更することもできます(すでにメソッドが何もする必要のないメソッドを返すだけであれば)。 – Servy

+0

なので、次のような意味になります。 foreach(実装者のvar impl) { var context = ...; try { tasks.Add(impl.DoSomethingAsync(context)); } catch(例外e) { tasks.Add(Task.FromException(e)); } } –

関連する問題