多线程性能提升
本文关键字:性能 多线程 | 更新日期: 2023-09-27 18:08:38
有人能告诉我为什么这些doccomputation Methods中的一个比另一个快得多(比如快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();
});
}
doccomputation的第二个例子,我使用一个类作为链接传递一个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);
}
和改进的doccalculation Method:
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
方法执行时发生的事情:
- ThreadQueue。QueueUserWorkItem创建一个新线程,
DoCalculation
方法退出。现在有一个后台线程正在运行。我们将此线程命名为1。 - 代码调用
DownloadDataInBackground
。该方法启动另一个线程以异步下载数据。调用线程2。 - 线程1退出。
- 当线程2完成下载,它调用完成回调,这再次调用
DownloadDataInBackground
。创建线程3,开始执行,线程2退出。 - 当线程3完成下载,它调用完成回调,做计算,输出一些数据,退出。
所以你启动了三个线程。那时还没有任何有意义的"多线程"。也就是说,没有一个以上的线程在做有意义的工作。
您的任务是顺序执行的,因此没有理由启动多个线程来运行。
如果你只写:
你的代码会更干净,执行速度会更快(因为不需要启动那么多线程):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
查询的默认值。
有了这种类型的代码,你通常不需要担心所有的MREs,并且代码非常容易阅读,可以更容易地进行测试。
Rx是一个受支持的微软产品,可以在桌面CLR和Silverlight上运行。
以下是Rx的链接:
- 响应式扩展论坛
- 响应扩展(Rx) v1.0.10621
哦,我认为你得到非常不同的性能结果的原因是Silverlight只有毫秒级的计时分辨率,所以你真的必须运行数千次计算才能得到一个好的平均值。
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 };
当然,这三个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
参数是不必要的,只是为了显式地显示查询使用线程池执行。