在.net 4.0中使用任务并行库,我想知道这种情况的最佳解决方案是什么:
我的代码正在启动执行大量长时间运行步骤的任务(步骤需要一个接一个地完成)。 我有一个对象Result,它汇总了每个步骤的结果。 结果对象在任务中被修改(因此在与此任务相关的线程中)。 我还有一个Web服务,我们可以在其中获取当前的Result对象以查看任务的进度。 所以Result对象是任务和我的代码的主线程之间的共享对象。实现这一目标的最佳方法是什么,以确保我没有线程问题和类似的东西?
这是我正在谈论的样本。请注意,_doWork不会像代码中那样是静态的,它将成为层次结构中更高级别的另一个类的成员。
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class Step1Result
{
}
public class Step2Result
{
}
public class Result
{
public Step1Result Step1Result;
public Step2Result Step2Result;
}
class DoWork
{
public Result Result;
public DoWork()
{
Result = new Result();
}
public void Process()
{
// Execute Step 1
Result.Step1Result = Step1();
Result.Step2Result = Step2();
// Other Steps ( long - running )
}
public Step1Result Step1()
{
// Long running step that can takes minutes
return new Step1Result();
}
public Step2Result Step2()
{
// Long running step that can takes minutes
return new Step2Result();
}
}
class Program
{
private static DoWork _doWork;
static void Main(string[] args)
{
_doWork = new DoWork();
var task = Task.Factory.StartNew(() => _doWork.Process());
task.Wait();
}
// This method will be called from a web service at anytime.
static Result CalledFromWebService()
{
return _doWork.Result;
}
}
}
这里遇到的麻烦是从Task和Main线程访问_doWork.Result。对吗?有什么办法可以解决这个问题?
答案 0 :(得分:0)
我会将DoWork.Result属性更改为GetCurrentResult()方法,并在每次返回当前操作结果的新副本时返回(您可以使用MemberwiseClone复制对象)。我没有看到任何需要共享相同的对象。
附加,我会使用ReadWriteLockSlim。所以DoWork类看起来像这样
class DoWork
{
private readonly Result _result;
private readonly ReadWriteLockSlim _lock = new ReadWriteLockSlim();
public DoWork()
{
_result = new Result();
}
public void Process()
{
// Execute Step 1
Step1Result st1result = Step1();
try
{
_lock.EnterWriteLock();
_result.Step1Result = st1result;
}
finally
{
_lock.ExitWriteLock();
}
Step2Result st2result = Step2();
try
{
_lock.EnterWriteLock();
_result.Step2Result = st2result;
}
finally
{
_lock.ExitWriteLock();
}
// Other Steps ( long - running )
}
public Step1Result Step1()
{
// Long running step that can takes minutes
return new Step1Result();
}
public Step2Result Step2()
{
// Long running step that can takes minutes
return new Step2Result();
}
public Result GetCurrentResult()
{
try
{
_lock.EnterReadLock();
return (Result)_result.MemberwiseCopy();
}
finally
{
_lock.ExitReadLock();
}
}
}
答案 1 :(得分:0)
如果我正确理解了问题,则访问Result对象时没有线程安全问题。
正如你所说,这些步骤必须一个接一个地完成,所以你将无法同时运行它们。
因此,在Process()
内,您可以在任务中启动Step1,然后在另一个任务中使用Step2启动.Continue
等等
因此,您只有一个编写器线程,并且没有并发问题。在这种情况下,如果您有另一个线程访问它并不重要 结果,如果这是一个只读的提取线程
如果从不同的线程访问集合,您只需要像ConcurrentDictionary这样的并发集合来存储结果。
如果步骤不是一个接一个地运行并且你有多个编写器,那么你只需要一个ReadWriteLockSlim
答案 2 :(得分:0)
这里唯一关心的是从Result
返回的CalledFromWebService
对象的脏读。您可以向Result
对象添加布尔属性,并删除对锁的需要:
public class Result
{
public volatile bool IsStep1Valid;
public Step1Result Step1Result;
public volatile bool IsStep2Valid;
public Step2Result Step2Result;
}
布尔值的赋值是原子的,因此您不必担心脏读和写。然后,您可以在Process
方法中使用这些布尔值,如下所示:
public void Process()
{
// Execute Step 1
Result.Step1Result = Step1();
Result.IsStep1Valid = true;
Result.Step2Result = Step2();
Result.IsStep2Valid = true;
// Other Steps ( long - running )
}
请注意,IsStep1Valid
的分配是在Step1Result
的分配之后确保Step1Result
在IsStep1Valid
设置为true之前从任务分配给它的值
现在,当您通过调用CalledFromWebService
访问主线程中的结果时,您只需执行以下操作:
void MyCode() {
var result = Program.CalledFromWebService();
if (result.IsStep1Valid) {
// do stuff with result.Step1Result
} else {
// if need be notify the user that step 1 is not complete yet
}
if (result.IsStep2Valid) {
// do stuff with result.Step2Result
}
// etc.
}
在尝试访问IsStep1Valid
属性之前检查Step1Result
的值可确保您没有弄脏Step1Result
属性。
更新:单独的Web服务无法访问Windows服务中的结果对象,因为它们在不同的应用程序域中运行。您需要从Windows服务中公开Web服务,并让Windows服务的主线程加载Web服务并调用您的后台任务。您不必公开公开此Web服务。您仍然可以在IIS中或您最初预期的位置托管Web服务。它只会调用由windows服务托管的Web服务。