私は読んでいますTask.Yield
、そしてJavascript開発者として、私はそれが仕事であると言うことができますまさにと同じsetTimeout(function (){...},0);
メインのシングルスレッドに他のものを処理させるという意味では別名:
「全力を尽くすな。時間から解放する。他の人はそうするだろう。 いくつかもあります…」
jsでは、それは特に長いループで働いています。 (ブラウザをフリーズさせないでください。)
しかし、私はこの例を見ましたここに:
public static async Task < int > FindSeriesSum(int i1)
{
int sum = 0;
for (int i = 0; i < i1; i++)
{
sum += i;
if (i % 1000 == 0) ( after a bulk , release power to main thread)
await Task.Yield();
}
return sum;
}
JSプログラマーとして、私は彼らがここで何をしたのか理解することができます。
しかしC#プログラマーとして私は自分自身に尋ねる:なぜそれのためにタスクを開かないのですか?
public static async Task < int > FindSeriesSum(int i1)
{
//do something....
return await MyLongCalculationTask();
//do something
}
質問
Jsではタスクを開くことができません(はい私は私が実際にウェブワーカーと一緒にできることを知っています)しかし、C#で私は出来ます。
もしそうなら - 私が全然それを解放することができる間に時々解放することさえ気にしないのはなぜ?
参照を追加する
からここに:
からここに(別の電子ブック):
あなたが見るとき:
await Task.Yield();
あなたはこのようにそれについて考えることができます:
await Task.Factory.StartNew(
() => {},
CancellationToken.None,
TaskCreationOptions.None,
SynchronizationContext.Current != null?
TaskScheduler.FromCurrentSynchronizationContext():
TaskScheduler.Current);
これらすべてが、継続が将来非同期に行われることを確実にすることです。によって非同期に実行制御は、呼び出し元に戻ります。async
メソッド、そして継続コールバックはではない同じスタックフレームで起こります。
正確にそしてどのスレッドにそれが起こるかについては完全に呼び出し側スレッドの同期コンテキストに依存します。
UIスレッドの場合継続は、メッセージループの将来の反復時に発生します。Application.Run
(WinFormまたは)Dispatcher.Run
(WPF)内部的には、それはWin32に帰着しますPostMessage
カスタムメッセージをUIスレッドのメッセージキューにポストするAPI。のawait
このメッセージが送信され処理されると、継続コールバックが呼び出されます。まさにこれがいつ起こるのかについて、あなたは完全に制御不能です。
その上、ウィンドウズはメッセージをポンピングするためにそれ自身の優先順位を持っています:情報:ウィンドウメッセージの優先順位。最も重要な部分:
このスキームでは、優先順位付けは3段階と見なすことができます。すべて 投稿されたメッセージは、ユーザーの入力メッセージよりも優先度が高くなります。 それらは異なるキューに常駐しています。そして、すべてのユーザー入力メッセージは WM_PAINTおよびWM_TIMERメッセージより高い優先順位。
だから、あなたが使用する場合await Task.Yield()
UIを反応させ続けるためにメッセージループに頼るためには、実際にはUIスレッドのメッセージループを妨害する危険があります。保留中のユーザー入力メッセージ、およびWM_PAINT
そしてWM_TIMER
、投稿された継続メッセージより低い優先順位を持っています。したがって、あなたがそうするならばawait Task.Yield()
きついループでは、まだUIをブロックする可能性があります。
これがJavaScriptのそれとどう違うかです。setTimer
あなたが質問で言及したのと同じように。 AsetTimer
コールバックが呼び出されます後にすべてのユーザ入力メッセージはブラウザのメッセージポンプによって処理されています。
そう、await Task.Yield()
UIスレッドでバックグラウンド作業をするのには向いていません。実際、UIスレッドでバックグラウンドプロセスを実行する必要はほとんどありませんが、実行することもあります。この場合は、フレームワークのアイドル状態のインフラストラクチャを使用してください。
例えば、WPFを使えばあなたはできるawait Dispatcher.Yield(DispatcherPriority.ApplicationIdle)
:
async Task DoUIThreadWorkAsync(CancellationToken token)
{
var i = 0;
while (true)
{
token.ThrowIfCancellationRequested();
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
// do the UI-related work item
this.TextBlock.Text = "iteration " + i++;
}
}
WinFormsでは、あなたは使用することができますApplication.Idle
イベント:
// await IdleYield();
public static Task IdleYield()
{
var idleTcs = new TaskCompletionSource<bool>();
// subscribe to Application.Idle
EventHandler handler = null;
handler = (s, e) =>
{
Application.Idle -= handler;
idleTcs.SetResult(true);
};
Application.Idle += handler;
return idleTcs.Task;
}
それをお勧めしますUIスレッドで実行されているこのようなバックグラウンド操作の繰り返しごとに50ミリ秒を超えないようにしてください。
非UIスレッドの場合同期コンテキストなしawait Task.Yield()
継続をランダムプールスレッドに切り替えるだけです。それがになるという保証はありません。違う現在のスレッドからのスレッド、それは非同期継続。もしThreadPool
飢えている、それは同じスレッドへの継続をスケジュールすることがあります。
ASP.NETでしているawait Task.Yield()
で説明されている回避策を除いて、まったく意味がありません。@ StephenClearyの答え。それ以外の場合は、冗長なスレッド切り替えを使用した場合にのみWebアプリケーションのパフォーマンスが低下します。
だから、ですawait Task.Yield()
有用?IMO、あまりありません。次のようにして継続を実行するためのショートカットとして使用できます。SynchronizationContext.Post
またはThreadPool.QueueUserWorkItem
あなたが本当にあなたのメソッドの一部に非同期を課す必要があるならば。
あなたが引用した本に関して、私の意見では、使用するためのこれらのアプローチTask.Yield
間違っている。私は上で、なぜそれらがUIスレッドにとって間違っているのかを説明しました。非UIプールスレッドの場合、単純にはありません。「実行するスレッド内の他のタスク」あなたがのようなカスタムタスクポンプを実行していない限り、スティーブントゥーブAsyncPump
。
コメントに答えるために更新されました:
...どうやって非同期操作と同じスレッドに留まることができるのか ?
簡単な例として:WinFormsアプリ:
async void Form_Load(object s, object e)
{
await Task.Yield();
MessageBox.Show("Async message!");
}
Form_Load
呼び出し元(起動したWinFromsフレームワークコード)に戻ります。Load
これにより、メッセージボックスが実行されるメッセージループの将来の反復で、メッセージボックスが非同期に表示さApplication.Run()
。継続コールバックは、キューに入れられます。WinFormsSynchronizationContext.Post
これは、プライベートWindowsメッセージをUIスレッドのメッセージループに内部的に投稿します。このメッセージが送信されたとき、まだ同じスレッド上でコールバックが実行されます。
コンソールアプリケーションでは、次のようにして同様のシリアル化ループを実行できます。AsyncPump
上記の通り。
SynchronizationContext
。見ましたかAsyncPump
?結局のところ、非同期はマルチスレッドを想定していません。それは、将来的に成果をもたらすものにすぎません。あなたが対処するときでも、注意してくださいThreadPool
s.contextがないと、継続が同じスレッドで発生する可能性があります(たとえば、同じプールスレッドが開始し、後で非同期I / O操作の完了に役立つことがあります)。 - noseratioprotected virtual Task OverridableMethod() { return Task.Task.CompletedTask; }
。注意async
これは仮想メソッドシグネチャの一部ではないため、派生クラスでこのメソッドをオーバーライドするときにも使用できます。 - noseratio
見つけただけTask.Yield
2つのシナリオで役立ちます。
いいえ、使用するのとは異なりますsetTimeout
UIに制御を戻します。 Javascriptでは、常にUIを次のように更新します。setTimeout
常に数ミリ秒以上の一時停止があり、保留中のUI作業はタイマーより優先されますawait Task.Yield();
そうではありません。
利回りがメインスレッドで実行されることを保証するものではありません。反対に、利回りを呼び出すコードがUI作業より優先されることがよくあります。
「ほとんどのUIのUIスレッドに存在する同期コンテキスト 多くの場合、環境はコンテキストに投稿された作業を優先します 入力やレンダリング作業よりも。このため、待つことに頼らないでください。 Task.Yield(); UIを即応させるため」
AsParallel
方法。 - Guffa
まず最初に明確にさせてください。Yield
とまったく同じではありませんsetTimeout(function (){...},0);
。 JSはシングルスレッド環境で実行されるので、それが他のアクティビティを実行させる唯一の方法です。やや協調マルチタスク。 .netは、明示的なマルチスレッドを使用したプリエンプティブマルチタスク環境で実行されます。
今に戻るThread.Yield
。私が言ったように、.netは先制の世界に住んでいますが、それはそれより少し複雑です。 C#await/async
ステートマシンによって支配されるそれらのマルチタスクモードの面白い混合を作成しなさい。省略した場合Yield
あなたのコードからそれはただスレッドをブロックするでしょう、そしてそれはそれです。通常のタスクにしてstart(またはスレッド)を呼び出すだけで、それが並列に行われ、後でtask.Resultが呼び出されたときにスレッドの呼び出しをブロックします。あなたがするとどうなりますかawait Task.Yield();
もっと複雑です。論理的には呼び出し側のコードのブロックを解除し(JSと同様)、実行が続行されます。それが実際に何をしているか - それは別のスレッドを選択し、呼び出しスレッドを使ってプリエンプティブ環境でその中で実行を継続します。それで最初までスレッドを呼び出していますTask.Yield
そしてそれはそれ自身の上にあります。その後の呼び出しTask.Yield
どうやら何もしないようです。
簡単なデモ:
class MainClass
{
//Just to reduce amont of log itmes
static HashSet<Tuple<string, int>> cache = new HashSet<Tuple<string, int>>();
public static void LogThread(string msg, bool clear=false) {
if (clear)
cache.Clear ();
var val = Tuple.Create(msg, Thread.CurrentThread.ManagedThreadId);
if (cache.Add (val))
Console.WriteLine ("{0}\t:{1}", val.Item1, val.Item2);
}
public static async Task<int> FindSeriesSum(int i1)
{
LogThread ("Task enter");
int sum = 0;
for (int i = 0; i < i1; i++)
{
sum += i;
if (i % 1000 == 0) {
LogThread ("Before yield");
await Task.Yield ();
LogThread ("After yield");
}
}
LogThread ("Task done");
return sum;
}
public static void Main (string[] args)
{
LogThread ("Before task");
var task = FindSeriesSum(1000000);
LogThread ("While task", true);
Console.WriteLine ("Sum = {0}", task.Result);
LogThread ("After task");
}
}
結果は次のとおりです。
Before task :1
Task enter :1
Before yield :1
After yield :5
Before yield :5
While task :1
Before yield :5
After yield :5
Task done :5
Sum = 1783293664
After task :1
動いたらTask.Yield
メソッドの上にそれは最初から非同期になり、呼び出し元のスレッドをブロックしません。
結論:Task.Yield
同期コードと非同期コードを混在させることができます。多かれ少なかれ現実的なシナリオ:あなたはいくらかの重い計算操作とローカルキャッシュとタスクを持っていますCalcThing
。このメソッドでは、アイテムがキャッシュにあるかどうかをチェックし、そうであれば - 存在しない場合はアイテムを返します。Yield
そしてそれを計算してください。実際にあなたの本からのサンプルはそこでは何も役に立つことが達成されていないのでかなり無意味です。 GUIのインタラクティビティに関する彼らの発言は、単なるひどく不適切なものです(UIスレッドは、最初の呼び出しまでロックされます)。Yield
、あなたはそれをするべきではありません、MSDNその上で明確(そして正しい)です: "待たされたTask.Yield()に頼らないでください; UIを敏感に保つために"
setTimeout(...,0 )
- Royi Namir
あなたは、長期実行関数がバックグラウンドスレッドで実行できるものであると仮定しています。そうでなければ、例えば、それがUIインタラクションを持っているので、それから実行中にUIをブロックするのを防ぐ方法がないので、実行時間はユーザに問題を引き起こさないように十分に短く保たれるべきです。
もう1つの可能性は、バックグラウンドスレッドよりも長期実行関数があることです。そのようなシナリオでは、これらの関数のいくつかがすべてのスレッドを占有しないようにすることをお勧めします。
await
。右 ? - Royi NamirLongRunning
それよりもむしろyield
あらゆる所に。 - Servy
Task.Yieldを使用する場合、誰も本当の答えを提供しなかったと思います。 タスクが終わりのないループ(または長時間の同期ジョブ)を使用し、スレッドプールスレッドを排他的に保持できる(他のタスクがこのスレッドを使用することを許可しない)場合は、主に必要です。これは、ループ内でコードが同期的に実行されると発生する可能性があります。のTask.Yieldのスケジュール変更スレッドプールキューへのタスクおよびスレッドを待っていた他のタスクを実行できます。
例:
CancellationTokenSource cts;
void Start()
{
cts = new CancellationTokenSource();
// run async operation
var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
// wait for completion
// after the completion handle the result/ cancellation/ errors
}
async Task<int> SomeWork(CancellationToken cancellationToken)
{
int result = 0;
bool loopAgain = true;
while (loopAgain)
{
// do something ... means a substantial work or a micro batch here - not processing a single byte
loopAgain = /* check for loop end && */ cancellationToken.IsCancellationRequested;
if (loopAgain) {
// reschedule the task to the threadpool and free this thread for other waiting tasks
await Task.Yield();
}
}
cancellationToken.ThrowIfCancellationRequested();
return result;
}
void Cancel()
{
// request cancelation
cts.Cancel();
}
Task.Yield()
彼らの仲間に実行する機会を与えるため。このような同時CPUバウンドシナリオには、より良い選択肢があります。Parallel
またはTPLデータフロー。 - noseratio
Task.Yield
UIを反応させ続けることはできません。また、async
そしてawait
UIスレッドを解放しないでください。計算時間が長い場合は、使用する必要がありますTask.Run
。 - Stephen Clearyawait Task.Yield()
毎秒、それから他の実行時間の短いタスクは、キューに長時間保留されずにチャンスを得るはずです。これは公平な行動ではないでしょうか。 - springy76await Task.Yield()
。 - Stephen Cleary