我创建了一个执行Web请求并将结果存储到数据库中的活动。通常这个过程大约需要1个小时,这会使工作流引擎表现异常。我发现对于这些长时间运行的活动,我应该编写一些不同的代码,以便不会阻止工作流引擎线程。
研究一些关于编写长时间运行活动的博客我理解我应该使用Bookmark
概念。但我没有使用TPL和Task
的任何解决方案。
此代码是否正确使用Task
s处理长时间运行的活动?
public sealed class WebSaveActivity : NativeActivity
{
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark("websave", (activityContext, bookmark, value) =>
{
});
Task.Factory.StartNew(() =>
{
GetAndSave(); // This takes 1 hour to accomplish.
context.RemoveBookmark("websave");
});
}
protected override bool CanInduceIdle
{
get
{
return true;
}
}
}
答案 0 :(得分:3)
不,这不是应该使用书签的方式。当工作流必须等待来自外部进程的输入时,将使用书签。
例如:我有一个文档审批工作流程,并且在某个时间点,工作流程必须等待人工审阅者对文档进行确认。而不是将工作流实例保留在内存中,当调用ResumeBookmark
时,工作流将被运行时空闲并再次激活。
在您的情况下,您的工作流不能被闲置,因为它在其上下文中运行了一个操作。顺便说一下,这个操作是你的任务,是一个即发即弃的任务,因此WF无法处理关键的失败。
现在,对于可能的解决方案,您可以考虑让其他进程调用GetAndSave
方法,并让该进程最终调用WF上的ResumeBookmark
,以便运行时可以使工作流空闲。该过程甚至可以与托管您的工作流程相同。
有关示例,请参阅this blogpost。只需想象一下,而不是等待人类在控制台中输入某些内容,而是执行长时间运行的任务。
您未指定活动后的内容,但请注意,在恢复书签时可以将数据返回到工作流程。因此,GetAndSave
的任何结果,即使它只是一个错误代码,您可以使用它来决定如何进一步处理工作流中的其他活动。
希望这对你有意义,你会看到我试图概述的可能的解决方案。
修改强>
关于在WF中使用Tasks或async / await的快速说明。 AFAIK没有方法可以覆盖返回任务,因此您必须使用.Wait()
或.Result
来阻止它们,或者忘掉它。因为如果你不能等待他们在工作流程执行期间会发生不好的事情,因为在使用Tasks完成其工作之前可能会启动其他活动。
当开发WF运行时时,Task的整个概念仍然非常年轻,因此WF运行时没有/不适合它们。
编辑2: 示例实现(基于this excellent official documentation)
您的活动几乎是空的:
public sealed class TriggerDownload : NativeActivity<string>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context)
{
// Create a Bookmark and wait for it to be resumed.
context.CreateBookmark(BookmarkName.Get(context),
new BookmarkCallback(OnResumeBookmark));
}
protected override bool CanInduceIdle
{
get { return true; }
}
public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
{
// When the Bookmark is resumed, assign its value to
// the Result argument. (This depends on whether you have a result on your GetData method like a string with a result code or something)
Result.Set(context, (string)obj);
}
}
它向工作流运行时发出信号,告知工作流可以闲置以及如何恢复它。
现在,对于工作流运行时配置:
WorkflowApplication wfApp = new WorkflowApplication(<Your WF>);
// Workflow lifecycle events omitted except idle.
AutoResetEvent idleEvent = new AutoResetEvent(false);
wfApp.Idle = delegate(WorkflowApplicationIdleEventArgs e)
{
idleEvent.Set();
};
// Run the workflow.
wfApp.Run();
// Wait for the workflow to go idle before starting the download
idleEvent.WaitOne();
// Start the download and resume the bookmark when finished.
var result = await Task.Run(() => GetAndSave());
BookmarkResumptionResult result = wfApp.ResumeBookmark(new Bookmark("GetData"), result);
// Possible BookmarkResumptionResult values:
// Success, NotFound, or NotReady
Console.WriteLine("BookmarkResumptionResult: {0}", result);
答案 1 :(得分:1)
我刚看到您的相关问题:How to write a long running activity to call web services in WF 4.0
另一种方法是将您的活动实施为AsyncCodeActivity
:
namespace MyLibrary.Activities
{
using System;
using System.Activities;
public sealed class MyActivity : AsyncCodeActivity
{
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var delegateToLongOperation = new Func<bool>(this.LongRunningSave);
context.UserState = delegateToLongOperation;
return delegateToLongOperation.BeginInvoke(callback, state);
}
protected override void EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var longOperationDelegate = (Func<bool>) context.UserState;
var longOperationResult = longOperationDelegate.EndInvoke(result);
// Can continue your activity logic here.
}
private bool LongRunningSave()
{
// Logic to perform the save.
return true;
}
}
}
工作流实例保留在内存中,但至少工作流运行时可以处理其正常的调度任务,而其中没有一个线程被长时间运行的进程占用。