[英]Multithread performance boost
有人可以告訴我為什么這些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));
}
});
}
然后,我有一個創建另一個線程以按順序進行一些計算的方法,這就是我需要前一個線程的結果繼續下一個操作,我發現了兩種方法可以做到這一點,這是針對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的第二個示例,我使用一個類作為鏈接,將Action作為參數傳遞給ThreadPool,並將其用作回調以在鏈中創建第二個和第三個線程:
鏈接類:
public class CalcParams
{
public int CallID;
public ManualResetEvent ManualReset;
public int Result;
public Action<int, ManualResetEvent, int> CallbackDone;
}
異步服務的示例:
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();
}
});
}
});
});
}
謝謝。
沒有充分的理由調用ThreadPool.QueueUserWorkItem
,然后立即等待它完成。 也就是說,編寫此代碼:
ThreadPool.QueueUserWorkItem(() =>
{
// do stuff
mevent.Set();
});
mevent.WaitOne();
不會給您任何好處。 您的主線程最終等待。 實際上,這比僅僅編寫還要糟糕:
// do stuff
因為線程池必須啟動一個線程。
您可以通過刪除所有嵌套的“異步”工作來簡化並加快您的第一個DoCalculation
方法:
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();
});
}
根據最新問題進行編輯
您的新示例#3在某種程度上簡化了操作,但仍然沒有抓住重點。 當您執行新的DoCalculation
方法時,將發生以下情況:
DoCalculation
方法退出。 現在,您正在運行一個后台線程。 我們將其稱為線程1。 DownloadDataInBackground
。 該方法啟動另一個線程以異步方式下載數據。 調用該線程2。 DownloadDataInBackground
。 創建線程3,開始執行,然后線程2退出。 因此,您啟動了三個線程。 從來沒有任何有意義的“多線程”正在進行。 也就是說,在任何時候都沒有一個以上的線程在從事有意義的工作。
您的任務是按順序執行的,因此沒有必要啟動多個線程來運行它們 。
如果您只是編寫以下代碼,則代碼將更加簡潔,執行速度會更快(由於不必啟動太多線程):
ThreadPool.QueueUserWorkItem((obj0) =>
{
DownloadString(...); // NOT DownloadStringAsync
DownloadString(...);
// Do calculation
});
一個線程按順序執行每個任務。
唯一需要多個線程的情況是,是否要同時執行多個任務。 顯然,這不是您在做什么。 實際上,您的問題是:
然后,我有一個創建另一個線程以按順序進行一些計算的方法,也就是說,我需要前一個線程的結果才能繼續下一個線程。
順序任務意味着一個線程。
我是否可以建議您將反應性擴展(Rx)視為在Silverlight中使用多線程的另一種方法?
這是您在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的鏈接:
哦,我認為您之所以會獲得非常不同的性能結果,是因為Silverlight僅具有毫秒級的計時分辨率,因此您實際上必須進行數千次計算才能獲得良好的平均值。
編輯 :根據注釋中的請求,這是一個使用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 };
當然,三個lambda函數可以很容易地成為常規方法函數。
如果您具有使用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 };
前面有點凌亂,但查詢有點簡單。
注意: Scheduler.ThreadPool
參數也是不必要的,但僅包含它即可明確顯示查詢是使用線程池執行的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.