我是一个专注于数据库的团队中唯一的应用程序开发人员。最近,我一直在努力提高我的前任原型制作过程的效率。做到这一点的最好方法是线程化。所以这是我的方法:
public void DoSomething()
{
Parallel.ForEach(rowCollection), (fr) =>
{
fr.Result = MyCleaningOperation();
});
}
哪个功能正常,但会导致错误。这些错误是在呼叫编码的第三方工具中产生的。这个工具应该是线程安全的,但看起来好像它们是在两个线程尝试同时执行相同的操作时出现的。
所以我回到了原型。以前我只是看了这个,看看如何与第三方工具交谈。但是当我检查被调用的代码时,我发现我的前任使用Task
和Action
使用我不熟悉的运算符进行了线程化。
Action<object> MyCleaningOperation = (object obj) =>
{
// invoke the third-party tool.
}
public void Main()
{
Task[] taskCollection = new Task[1];
for (int i = 0; i < rowCollection.Length; i++)
{
taskCollection[i] = new Task(MyCleaningOperation, i);
}
foreach (var task in taskCollection)
{
task.Start();
}
try
{
Task.WaitAll(taskCollection);
}
catch (Exception ex)
{
throw ex;
}
}
现在,这不是很好的代码,但它是一个原型。据称他的原型没有错误,并且比我的速度更快。我无法验证这一点,因为他的原型依赖于不再存在的开发数据库。
我并不特别想在我的应用程序中尝试不同类型的线程,以查看是否有些错误 - 它们是间歇性的,所以这将是一个漫长的过程。更重要的是,因为阅读了Task
,我看不出有什么理由比Parallel
更有效。因为我正在使用void
函数,所以我无法轻易添加await
来模仿原型操作。
那么:两者之间是否存在运营差异?或者任何其他原因导致工具使用相同的资源而不是使用多个线程而导致工具绊倒?
答案 0 :(得分:3)
Action<T>
是一个返回void的委托,它占用T
。它表示一个消耗T
,不产生任何内容的操作,并在调用时启动。
Task<T>
就是它所说的:它代表了一项可能尚未完成的工作,当它完成时,它会为其完成提供T
。
所以,让我们确保你到目前为止: Task<T>
的完成情况是什么?
在你把它搞砸之前不要继续阅读。
任务的完成就是一个动作。一个任务在将来产生一个T;一个动作在可用时对该T执行动作。
好的,那么什么是Task
,没有T
? not 的任务在完成时会生成一个值。 Task
完成的是什么?简单地说是Action
。
我们如何描述Task
执行的任务呢?它做了一些但没有产生任何结果。那是Action
。假设任务要求它使用一个对象来完成它的工作;那是Action<object>
。
确保您了解这里的关系。它们有点棘手,但它们都有意义。这些名字都经过精心挑选。
那么什么是线程呢? 线程是可以执行任务的工作者。 不要将任务与线程混淆。
阅读有关任务的内容后,我看不出有什么理由比Parallel更有效。
你明白我的意思。这句话毫无意义。任务就是:任务。将此书交给此地址。添加这些数字。割草坪。任务不是工人,他们肯定不是“聘请一群工人来完成任务”的概念。 并行性是一种为员工分配任务的策略。
此外,不要陷入相信任务本质上是平行的陷阱。 不要求多个工作人员同时执行任务;我们在过去几年中在C#中所做的大部分工作都是确保任务可以由单个工作人员有效地执行。如果您需要做早餐,割草坪并收取邮件,您不需要雇佣员工来做这些事情,但您仍然可以在吐司敬酒时收取邮件。
您应该仔细检查您声称提高性能的最佳方法是并行化。请记住,并行化只是雇用与运行它们的CPU核心一样多的工作人员,然后将任务分发给每个人。这只是一个改进,如果(1)任务实际上可以独立并行运行,(2)任务是在CPU上进行门控,而不是I / O,(3)你可以编写面向正确的程序同一程序中的多个执行线程。
如果你的任务真的是“令人尴尬的并行”并且可以完全独立地运行 那么你可能会考虑进程并行而不是线程并行。它更安全,更容易。
答案 1 :(得分:2)
错误是在呼叫编码的第三方工具中产生的。这个工具应该是线程安全的,但它看起来很强烈,好像它们在两个线程尝试同时执行相同的操作时会出现。
如果这是正确的,那么并行任务不会比Parallel
更能防止错误。
但是当我检查被调用的代码时,我发现我的前任已经使用了Task和Action来操作它,我并不熟悉这些操作符。
该代码看起来没问题,但它确实使用了与Start
结合的任务构造函数,这将更加优雅地表达Task.Run
。
原型正在使用dynamic task-based parallelism方法,这种情况太过分了。您的代码使用parallel loops,这更适合数据并行(请参阅Selecting the Right Pattern和Figure 1)。
据说他的原型没有错误,并且比我的速度更快。
如果错误是由多线程引起的,但在第三方工具中,则原型同样容易受到这些错误的影响。也许它使用的是该工具的早期版本,或者dev数据库中的数据没有公开该bug,或者它很幸运。
关于性能,我希望Parallel
通常具有优于普通任务并行性的优越性能,因为Parallel
可以&#34;批量&#34;任务之间的操作,减少开销。虽然这种额外的逻辑确实带来了成本,但对于小数据量而言,它可能性能较差。
IMO更大的问题是正确性,如果它失败了Parallel
,那么它可能很容易因并行任务而失败。
答案 2 :(得分:1)
从表面上看,任务和线程之间的区别是:
Task
或Task<T>
的上下文中的任务是在未来的某个时刻有可能完成的事物的表示,然后表示结果完成基本上就是这样。
当然,你可以将一个线程包装在一个任务中,但如果你的问题只是“一个线程和一个任务之间有什么区别”那么上面就是它。
您可以轻松地表示与任务中的线程无关或甚至并行执行代码的事情,它仍然是一项任务。异步I / O最近大量使用任务,其中大多数(至少是好的实现)根本不使用(额外的)线程。