2011-09-15 6 views
1

これらのDoCalculationメソッドのいずれかが他のものよりもはるかに高速(40%高速)の理由を教えてもらえますか?マルチスレッドパフォーマンスの向上

Iを設定するManualResetEvents待つメインスレッドを持っている:

private void LayoutRoot_Loaded(object sender, RoutedEventArgs e) 
{ 
ThreadPool.QueueUserWorkItem((obj) => 
{ 
    ManualResetEvent[] finishcalc = new ManualResetEvent[] 
    { 
     new ManualResetEvent(false), 
     new ManualResetEvent(false), 
     new ManualResetEvent(false), 
     new ManualResetEvent(false), 
     new ManualResetEvent(false), 
     new ManualResetEvent(false) 
    }; 
    TimeSpan time1 = new TimeSpan(DateTime.Now.Ticks); 
    DoCalculation(rand.Next(10), rand.Next(10), 1, finishcalc[0]); 
    DoCalculation(rand.Next(10), rand.Next(10), 2, finishcalc[1]); 
    DoCalculation(rand.Next(10), rand.Next(10), 3, finishcalc[2]); 
    DoCalculation(rand.Next(10), rand.Next(10), 4, finishcalc[3]); 
    DoCalculation(rand.Next(10), rand.Next(10), 5, finishcalc[4]); 
    DoCalculation(rand.Next(10), rand.Next(10), 6, finishcalc[5]); 

    if (WaitHandle.WaitAll(finishcalc)) 
    {    
     TimeSpan time2 =new TimeSpan(DateTime.Now.Ticks); 
     AddTextAsync(string.Format("DoCalculation Finish in {0}\n" ,(time2-time1).TotalSeconds)); 
    } 
}); 
} 

そして私はいくつかの計算を順次実行する別のスレッドを作成する方法を持って、これは私が以前からの結果が必要ですスレッドを使用して次のスレッドを続行します。これを行うには2つの方法があります。これはSilverlight用です。私は新しいスレッドを作成していますし、すべての連続した計算が継続する前に完了することが待って最初の例では

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone) 
{ 
    ThreadPool.QueueUserWorkItem((obj0) => 
    { 
     AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2)); 
     int result = 0; 
     ManualResetEvent mresetevent = new ManualResetEvent(false); 
     ThreadPool.QueueUserWorkItem((obj) => 
     { 
      result = number1 + number2; 
      mresetevent.Set(); 
     }); 
     mresetevent.WaitOne(); 
     mresetevent.Reset(); 
     ThreadPool.QueueUserWorkItem((obj2) => 
     { 
      result *= result; 
      mresetevent.Set(); 
     }); 
     mresetevent.WaitOne(); 
     mresetevent.Reset(); 

     ThreadPool.QueueUserWorkItem((obj2) => 
     { 
      result *= 2; 
      mresetevent.Set(); 
     }); 
     mresetevent.WaitOne(); 
     AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result)); 
     calcdone.Set(); 
    }); 
} 

DoCalculationの第二の例私はアクションを渡すためにリンクとしてクラスを使用するようにスレッドプールのパラメータとチェーンに第二および第三のスレッドを作成するコールバックとして使用:

リンククラス:

public class CalcParams 
{ 
    public int CallID; 
    public ManualResetEvent ManualReset; 
    public int Result; 
    public Action<int, ManualResetEvent, int> CallbackDone; 
} 

で、Asyの例NCサービス::

public static void DownloadDataInBackground(CalcParams calcparams) 
{ 
    WebClient client = new WebClient(); 
    Uri uri = new Uri("http://www.google.com"); 
    client.DownloadStringCompleted += (s, e) => 
    { 
     CalcParams localparams = (CalcParams)e.UserState; 
     localparams.CallbackDone(e.Result.Length + localparams.Result, localparams.ManualReset, localparams.CallID); 
    }; 
    client.DownloadStringAsync(uri, calcparams); 
} 

そして改善doCalculation方法:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone) 
{ 
    ThreadPool.QueueUserWorkItem((obj0) => 
    { 
     int result = number1+number2; 
     doCalculationService.DownloadDataInBackground(new CalcParams() 
     { 
      Result = result, 
      ManualReset = calcdone, 
      CallID = callid, 
      CallbackDone = (r, m, i) => 
      { 
       int sqrt = r * r; 
       doCalculationService.DownloadDataInBackground(new CalcParams() 
       { 
        Result = sqrt, 
        CallID = i, 
        ManualReset = m, 
        CallbackDone = (r2, m2, i2) => 
        { 
         int result2 = r2 * 2; 
         AddTextAsync(string.Format("The result for Callid {0} is {1} \n", i2, result2)); 
         m2.Set(); 
        } 
       }); 
      } 
     }); 
    }); 
} 

はありがとうございます。

+0

StopWatchを使用して、結果を同じにするかどうかに関わらず、時間を測定できますか?また、AddTextAsyncのコードを共有して、おそらくボトルネックがありますか? – sll

+0

私はストップウォッチがSilverlightで利用できないと信じています。私は他のプラットフォームでも同じことをテストできましたが、今はシルバーライトだけに興味があります。そして余計な呼び出しを取り除いても、2つのメソッド間の時間差があまりにも大きいので、なぜわかりません... – montelof

+0

これは、これがコード例です。QueueUserWorkItemがたくさんあります。実際、ハウスキーピングの費用は、あなたがやっている仕事量をはるかに上回っています。 – Skizz

答えて

2

Silverlightでマルチスレッドを使用する別の方法としてReactive Extensions(Rx)を使用することをお勧めしますか?ここで

はRxの中で行われ、あなたのコードです:

Func<int, int, int> calculation = (n1, n2) => 
{ 
    var r = n1 + n2; 
    r *= r; 
    r *= 2; 
    return r; 
}; 

var query = 
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool) 
    let n1 = rand.Next(10) 
    let n2 = rand.Next(10) 
    from result in Observable.Start(() => calculation(n1, n2)) 
    select new { callid, n1, n2, result }; 

query.Subscribe(x => { /* do something with result */ }); 

それは自動的にスレッドプールに出る計算をプッシュ - 私はScheduler.ThreadPoolパラメータを置くが、それはSelectManyクエリのデフォルトです。

この種のコードでは、一般的にすべてのMREについて心配する必要はなく、より簡単にテストできるコードを読みやすくなります。

RxはサポートされているMicrosoft製品で、デスクトップのCLRとSilverlightで動作します。

ここ

がRxのためのリンクです:

ああ、私はあなたが非常に異なる性能の結果を得ている理由はシルバーのみミリ秒を持っているということだと思いあなたが本当に良い平均を得るために数千回計算を実行しなければならないので、タイミングのための解像度。


EDITコメントで要求ごとのように、ここでのRxを用いて各中間計算結果を連鎖の一例です。

Func<int, int, int> fn1 = (n1, n2) => n1 + n2; 
Func<int, int> fn2 = n => n * n; 
Func<int, int> fn3 = n => 2 * n; 

var query = 
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool) 
    let n1 = rand.Next(10) 
    let n2 = rand.Next(10) 
    from r1 in Observable.Start(() => fn1(n1, n2)) 
    from r2 in Observable.Start(() => fn2(r1)) 
    from r3 in Observable.Start(() => fn3(r2)) 
    select new { callid, n1, n2, r1, r2, r3 }; 

もちろん、3つのラムダ関数は、簡単には通常のメソッド関数である可能性があります。

さらに別の、あなたはBeginInvoke/EndInvokeを使用する機能を持っている場合は非同期パターンは次のようにFromAsyncPattern拡張メソッドを使用することです:

Func<int, int, IObservable<int>> ofn1 = 
    Observable.FromAsyncPattern<int, int, int> 
     (fn1.BeginInvoke, fn1.EndInvoke); 

Func<int, IObservable<int>> ofn2 = 
    Observable.FromAsyncPattern<int, int> 
     (fn2.BeginInvoke, fn2.EndInvoke); 

Func<int, IObservable<int>> ofn3 = 
    Observable.FromAsyncPattern<int, int> 
     (fn3.BeginInvoke, fn3.EndInvoke); 

var query = 
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool) 
    let n1 = rand.Next(10) 
    let n2 = rand.Next(10) 
    from r1 in ofn1(n1, n2) 
    from r2 in ofn2(r1) 
    from r3 in ofn3(r2) 
    select new { callid, n1, n2, r1, r2, r3 }; 

フロントアップ少しメシエが、クエリが少しあります簡単です。

NB:再度Scheduler.ThreadPoolパラメータは不要ですが、スレッドプールを使用してクエリが実行されることを明示的に示すためにのみ含まれています。

+0

私は3ヶ月前に作業していたプロジェクトを開始したときにRXを使用することを検討していましたが、その時までにRXがSilverlight用に利用可能であったかどうかは分かりませんでした。再度尋ねる前に、どのスレッドも何も見つからなかったので、あなたを拡張してください。 3つの操作のそれぞれがAsynchで実行され、次の操作を実行するために前の操作の結果が必要であると想像してください。ありがとう。 – montelof

+0

@montelof - Rxと連動させるのは簡単です。私は要求通りの例で私の解決策を編集します。 :-) – Enigmativity

3

ThreadPool.QueueUserWorkItemに電話をかけてすぐに終了するまで待つ必要はありません。つまり、これを書いています:

ThreadPool.QueueUserWorkItem(() => 
    { 
     // do stuff 
     mevent.Set(); 
    }); 
mevent.WaitOne(); 

あなたには何の利益もありません。あなたのメインスレッドは待って終了します。実際には、それだけで書くよりも悪化します:

// do stuff 

スレッドプールがスレッドをスピンアップすることがあるため。

あなたはすべてのネストされた「非同期」の仕事を除去することによって、あなたの最初のDoCalculation方法簡素化し、スピードアップすることができます:更新された質問に対して

新しい例#3は、物事を単純化し

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone) 
{ 
    ThreadPool.QueueUserWorkItem((obj0) => 
    { 
     AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2)); 
     int result = 0; 

     result = number1 + number2; 
     result *= result; 
     result *= 2; 

     AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result)); 
     calcdone.Set(); 
    }); 
} 

編集をある程度は、しかし、まだポイントを逃す。ここにあなたの新しいDoCalculationメソッドが実行されたときに何が起こるかです:

  1. ThreadQueue.QueueUserWorkItemは、新しいスレッドとDoCalculationメソッドが終了を作成します。これで、バックグラウンドスレッドが実行されました。このスレッド1を呼び出します。
  2. コードはDownloadDataInBackgroundです。このメソッドは、スレッドを非同期的にダウンロードします。そのスレッド2を呼び出します。
  3. スレッド1終了します。
  4. スレッド2がダウンロードを完了すると、補完コールバックが呼び出され、再びDownloadDataInBackgroundが呼び出されます。スレッド3が作成され、実行が開始され、スレッド2が終了します。
  5. スレッド3がダウンロードを完了すると、完了コールバックが呼び出され、計算が実行され、データが出力されて終了します。

3つのスレッドを起動しました。同時に、意味のある「マルチスレッド」が続いていました。つまり、一度に複数のスレッドが意味のある作業をしていることはありませんでした。

あなたのタスクは順番に実行されているので、を実行するために複数のスレッドを開始する必要はありません。

あなたのコードは非常にクリーンになると(これは非常に多くのスレッドを開始する必要がないように)多少高速に実行でしょうあなただけ書いた場合:

ThreadPool.QueueUserWorkItem((obj0) => 
{ 
    DownloadString(...); // NOT DownloadStringAsync 
    DownloadString(...); 
    // Do calculation 
}); 

を1つのスレッドが順番に各タスクを実行します。

複数のスレッドが必要なのは、複数のタスクを同時に実行する場合だけです。明らかに、それはあなたがやっていることではありません。実際には、あなたの質問は言う:

私はいくつかの計算を順番に行う別のスレッドを作成する方法があります、これは、次のものを続行するには、前のスレッドの結果が必要です。

順次タスクは1つのスレッドを意味します。

+0

あなたの答えをありがとう、しかし、多分私は十分に明確ではなかった、これは、各計算がWebサービスへのAsyncronomous呼び出しを表す例です、または私たちが望んでいない時間のかかる作業他のスレッドを続行する前に、メインスレッドが待機するようにします。 – montelof

+0

私の場合、ソケットでサーバーとネットワーク通信を行っているOOBアプリケーションを使用している場合、socketclientはusing アクションをパラメータとして受け取り、コールバックメソッドから呼び出しスレッドを設定できます。呼び出し元ID、サーバーレスポンスなどの他のパラメータを渡すので、これらの値をパラメータとして次のスレッドに渡すことができます。ここでのアイデア全体は、非同期サービスの呼び出し方法に基づいてパフォーマンスがどのように変化するかです。 – montelof

+0

私はまだ理解していないと思います。各非同期呼び出しが以前の呼び出しから返された値に依存する場合(つまり、例のように連鎖している場合)は、私が示したように、すべてを1つの非同期ブロックにまとめることができます。いずれの例も、同時の非同期タスクを実行する 'DoCalculation'メソッドを示していないので、あなたが提案する複雑な連鎖の必要はありません。実際のアプリケーションの 'DoCalculation'メソッド*が並行非同期タスクを実行する場合は、その例を変更してそのことを示す必要があります。 –

関連する問題