基本的に、BackgroundWorker
に使用する匿名メソッドがあります。
worker.DoWork += ( sender, e ) =>
{
foreach ( var effect in GlobalGraph.Effects )
{
// Returns EffectResult
yield return image.Apply (effect);
}
};
これを行うと、コンパイラは次のように指示します。
「yieldステートメントは無名メソッドまたはラムダ式内では使用できません」
したがって、この場合、これを行うための最もエレガントな方法は何ですか?ところで、このDoWorkメソッドは、ソリューションにとって重要な場合に備えて、静的メソッドの内部にあります。
さて、私は私が望むことをするこのようなことをしました(いくつかの変数は省略されています):
public static void Run ( Action<float, EffectResult> action )
{
worker.DoWork += ( sender, e ) =>
{
foreach ( var effect in GlobalGraph.Effects )
{
var result = image.Apply (effect);
action (100 * ( index / count ), result );
}
}
};
次に、コールサイトで:
GlobalGraph.Run ( ( p, r ) =>
{
this.Progress = p;
this.EffectResults.Add ( r );
} );
残念ながらできません。
コンパイラでは、2つの「魔法の」コードを組み合わせることができません。どちらも、やりたいことをサポートするようにコードを書き直す必要があります。
ただし、コレクションを返すようにコードを書き直すことはできるので、特定の場合は次のようにします。
worker.DoWork += ( sender, e ) =>
{
return GlobalGraph.Effects
.Select(effect => image.Apply(effect));
};
イベントとしては奇妙に見えますが(sender, e)
何でも返す。あなたは私たちのために実際のシナリオを示していると確信していますか?
編集わかりました、私考えるここであなたがやろうとしていることがわかります。
静的メソッド呼び出しがあり、バックグラウンドでコードを実行し、バックグラウンド呼び出しが完了したら、その静的メソッドからデータを返します。
これは可能ですが、スレッドを一時停止する直前に開始されたスレッドを一時停止して別のスレッドを待機しているため、適切な解決策ではありません。言い換えれば、あなたがしているのは、コンテキスト切り替えのオーバーヘッドを追加することだけです。
代わりに、バックグラウンド作業を開始するだけで、その作業が完了したら、結果のデータを処理する必要があります。
おそらく、linq式を返し、yieldのように実行を延期するだけです。
return GlobalGraph.Effects.Select(x => image.Apply(x));
私が何かを逃していない限り、あなたはあなたが求めていることをすることはできません。
(私はあなたに答えを持っているので、あなたが最初にやっていることをできない理由についての私の説明を過ぎて読んでから、読んでください。)
完全なメソッドは次のようになります。
public static IEnumerable<EffectResult> GetSomeValues()
{
// code to set up worker etc
worker.DoWork += ( sender, e ) =>
{
foreach ( var effect in GlobalGraph.Effects )
{
// Returns EffectResult
yield return image.Apply (effect);
}
};
}
コードが「合法」であると仮定すると、GetSomeValues
ハンドラーがDoWork
に追加されていても、worker
が呼び出されると、ラムダ式はDoWork
イベントが発生します。したがって、GetSomeValues
の呼び出しは結果を返さずに完了し、lamdbaは後の段階で呼び出される場合とされない場合があります。これは、とにかくGetSomeValues
メソッドの呼び出し元には遅すぎます。
あなたの最良の答えは使用することです Rx 。
RxはIEnumerable<T>
を頭に向けます。 Rxは、列挙可能な値から値を要求する代わりに、IObservable<T>
から値をプッシュします。
バックグラウンドワーカーを使用してイベントに応答しているので、すでに値が効果的にプッシュされています。 Rxを使用すると、実行しようとしていることを簡単に実行できます。
いくつかのオプションがあります。おそらく最も簡単なのはこれを行うことです:
public static IObservable<IEnumerable<EffectResult>> GetSomeValues()
{
// code to set up worker etc
return from e in Observable.FromEvent<DoWorkEventArgs>(worker, "DoWork")
select (
from effect in GlobalGraph.Effects
select image.Apply(effect)
);
}
これで、GetSomeValues
メソッドの呼び出し元は次のようになります。
GetSomeValues().Subscribe(ers =>
{
foreach (var er in ers)
{
// process each er
}
});
DoWork
が1回だけ起動することがわかっている場合は、このアプローチの方が少し優れている可能性があります。
public static IObservable<EffectResult> GetSomeValues()
{
// code to set up worker etc
return Observable
.FromEvent<DoWorkEventArgs>(worker, "DoWork")
.Take(1)
.Select(effect => from effect in GlobalGraph.Effects.ToObservable()
select image.Apply(effect))
.Switch();
}
このコードはもう少し複雑に見えますが、単一のdoworkイベントをEffectResult
オブジェクトのストリームに変えるだけです。
すると、呼び出しコードは次のようになります。
GetSomeValues().Subscribe(er =>
{
// process each er
});
Rxは、バックグラウンドワーカーの代わりに使用することもできます。これはあなたにとって最良の選択肢かもしれません:
public static IObservable<EffectResult> GetSomeValues()
{
// set up code etc
return Observable
.Start(() => from effect in GlobalGraph.Effects.ToObservable()
select image.Apply(effect), Scheduler.ThreadPool)
.Switch();
}
呼び出しコードは前の例と同じです。 Scheduler.ThreadPool
は、オブザーバーへのサブスクリプションの処理を「スケジュール」する方法をRxに指示します。
これがお役に立てば幸いです。
DoWork
はタイプDoWorkEventHandler
で、何も返さない(void
)ので、あなたの場合はまったく不可能です。
ワーカーは、DoWorkEventArgsのResultプロパティを設定する必要があります。
worker.DoWork += (s, e) => e.Result = GlobalGraph.Effects.Select(x => image.Apply(x));
新しい読者の場合:C#5で「匿名イテレータ」(つまり、他のメソッドにネストされている)を実装する最もエレガントな方法は、おそらく async/awaitを使用したこのクールなトリック (と混同しないでください)のようなものです。これらのキーワード、以下のコードは完全に同期して計算されます-リンクされたページの詳細を参照してください):
public IEnumerable<int> Numbers()
{
return EnumeratorMonad.Build<int>(async Yield =>
{
await Yield(11);
await Yield(22);
await Yield(33);
});
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
public void TestEnum()
{
var v = Numbers();
var e = v.GetEnumerator();
int[] expected = { 11, 22, 33 };
Numbers().Should().ContainInOrder(expected);
}
C#7(Visual Studio 15プレビューで利用可能になりました)は yield return
を許可するローカル関数 をサポートします:
public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (filter == null) throw new ArgumentNullException(nameof(filter));
return Iterator();
IEnumerable<T> Iterator()
{
foreach (var element in source)
{
if (filter(element)) { yield return element; }
}
}
}