2011-09-15 5 views
1

이러한 DoCalculation 메서드 중 하나가 다른 것보다 왜 더 빨리 (40 % 더 빠름) 왜 왜 누군가가 말해 줄 수 있습니까? 멀티 스레드 성능 향상

내가 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)); 
    } 
}); 
} 

가 그럼 난 몇 가지 계산을 순차적으로 수행하는 다른 스레드를 만들 방법이를 본 나는 이전의 결과가 필요하다 thread를 사용하여 다음과 같은 작업을 계속할 수 있습니다. 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의 두 번째 예는 액션을 통과 ThreadPool이 행 변수 및 체인의 두 번째 및 세 번째 스레드를 생성하는 콜백으로 사용

링크 클래스하십시오 ASY의

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

예 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

나는 스톱워치를 실버 라이트에서 사용할 수 없다고 생각한다. 다른 플랫폼에서도 똑같은 것을 테스트 할 수는 있지만 지금은 실버 라이트에만 관심이있다. 그리고 여분의 호출을 제거하는 두 가지 방법간에 너무 많은 시간 차이가 있습니다. 이유는 알 수 없습니다. – montelof

+0

이 예제 코드는 QueueUserWorkItem이 많이 있습니다. 실제로, 관리 비용은 당신이하는 일의 양을 훨씬 초과합니다. – Skizz

답변

2

Silverlight에서 멀티 스레딩을 사용하는 대체 방법으로 Reactive Extensions (Rx)를 사용하는 것이 좋습니다. 내가있는 Scheduler.ThreadPool 매개 변수를 넣어, 그러나 그것은 SelectMany 쿼리의 기본이다 -

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 */ }); 

그것은 자동으로 스레드 풀에 나가 계산을 밀어 :

여기에 수신에서 수행 코드입니다.

이러한 종류의 코드를 사용하면 일반적으로 모든 MRE에 대해 걱정할 필요가 없으며보다 쉽게 ​​테스트 할 수있는 코드를 쉽게 읽을 수 있습니다.

Rx는 지원되는 Microsoft 제품이며 Silverlight는 물론 데스크톱 CLR에서도 실행됩니다.

아, 그리고 난 당신이 매우 다른 성능 결과를 얻고있는 이유는 실버 라이트는 밀리 초를 가지고 있다고 생각 : 여기

는 수신에 대한 링크입니다 타이밍에 대한 해상도가 있으므로 좋은 평균을 얻기 위해 실제로 수천 번 계산을 실행해야합니다.


EDIT는 : 주석의 요청에 따라, 현재 수신을 이용하여 각 중간 계산 결과를 체인의 일례이다.

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가 실버 라이트에서 사용 가능한지 몰랐고 다시 물어보기 전에 3 가지 작업을 연결하는 방법을 찾으려고 노력했습니다. 각 스레드의, 그리고 나는 어떤 예제를 찾을 수 없습니다, 당신은 당신의 확장하시기 바랍니다 수 있습니까? 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이 다운로드를 완료하면 완료 콜백을 호출하고 계산을 수행하고 데이터를 출력 한 다음 종료합니다.

그래서 세 개의 스레드를 시작했습니다. 어떤 의미있는 "멀티 쓰레딩"도 없었습니다. 즉, 의미있는 작업을하는 스레드가 두 개 이상 존재하지 않았습니다.

작업이 순차적으로 수행되므로을 실행하기 위해 여러 스레드를 시작할 이유가 없습니다.

코드는 훨씬 청소기 될 것 방금 쓴 경우 (인해 많은 스레드를 시작하지 않아도에) 빠른 다소 실행합니다 :

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

하나의 스레드 순서대로 각 작업을 수행합니다.

여러 스레드가 필요한 유일한 시간은 여러 작업을 동시에 실행하려는 경우입니다. 분명히, 그것은 당신이하는 일이 아닙니다. 사실, 귀하의 질문에 말합니다 :

다음 몇 가지 계산을 순차적으로 수행 할 다른 스레드를 만드는 방법,이 다음 스레드를 계속하려면 이전 스레드에서 결과가 필요합니다.

순차적 작업이란 하나의 스레드를 의미합니다.

+0

답장을 보내 주셔서 감사합니다.하지만 어쩌면 충분히 명확하지 않았습니다. 이것은 각 계산이 웹 서비스에 대한 Asyncronomous 호출을 나타내는 예제이거나 우리가 원하지 않는 시간 소모적 인 작업입니다. 주 스레드가 다른 스레드를 계속하기 전에 스레드가 대기 할 수 있도록하십시오. – montelof

+0

제 경우 메신저 소켓에 의해 서버와 네트워크 통신을하는 OOB 애플리케이션을 사용하는 소켓 클라이언트는 액션 매개 변수를 받아들이므로 콜백 메소드에서 콜링 스레드를 설정할 수 있습니다. 호출자 ID, 서버 응답 등과 같은 다른 매개 변수를 전달하면 이러한 값을 매개 변수로 전달하여 다음 스레드에 전달할 수 있습니다. 여기 전체 아이디어는 비동기 서비스를 호출하는 방식에 따라 성능이 어떻게 바뀌는지를 보여줍니다. – montelof

+0

나는 아직도 나는 이해하지 못한다고 생각한다. 각 비동기 호출이 이전 호출에서 반환 한 값 (예 : 체인에 연결)에 따라 다르면 내가 보여 준 것처럼 전체를 단일 비동기식 블록으로 래핑 할 수 있습니다. 어떤 예제도 동시 비동기 작업을 수행하는 DoCalculation 메서드를 보여주지 않으므로 사용자가 제안하는 복잡한 연결이 필요하지 않습니다. 실제 응용 프로그램의 DoCalculation 메서드 *가 동시 비동기 작업을 수행하는 경우이를 표시하도록 예제를 변경해야합니다. –