使用 TPL 处理 WF 4.0 long 运行 activity
Handling WF 4.0 long running activity using TPL
我创建了一个 activity 来执行网络请求并将结果存储到数据库中。通常这个过程大约需要 1 小时,它会使工作流引擎运行异常。我发现对于这些长时间的 运行 活动,我应该编写一些不同的代码,这样工作流引擎线程就不会被阻塞。
研究了一些关于写长运行活动的博客,我明白我应该使用Bookmark
概念。但是我没有使用 TPL 和 Task
.
的任何解决方案
此代码使用 Task
s 处理长 运行 activity 是否正确?
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;
}
}
}
不,这不是使用书签的方式。当工作流必须等待来自外部进程的输入时,将使用书签。
例如:我有一个文档审批工作流程,在某个时间点,工作流程必须等待人工审阅者对文档给予确定。当 ResumeBookmark
被调用时,工作流将被空闲并再次激活,而不是将工作流实例保存在内存中。
在您的情况下,您的工作流不能闲置,因为它在其上下文中有一个操作 运行。该操作是您的任务,顺便说一下,这是一个即发即弃的任务,因此 WF 无法处理严重故障。
现在,对于一个可能的解决方案,您可以考虑让另一个进程调用 GetAndSave
方法,并让该进程最终调用 WF 上的 ResumeBookmark
,这样工作流就可以被运行。该进程甚至可以是承载您的工作流程的同一进程。
有关示例,请参见 this blogpost。想象一下,您无需等待人在控制台中输入内容,而是执行了漫长的 运行 任务。
您没有指定 activity 之后的内容,但请注意,恢复书签后可以将 return 数据返回到工作流程。因此 GetAndSave
的任何结果,即使它只是一个错误代码,您都可以使用它来决定如何进一步处理工作流中的其他活动。
希望这对您有意义,并且您会看到我尝试概述的可能的解决方案。
编辑
关于在 WF 中使用任务或 async/await 的快速说明。据我所知,没有方法可以覆盖 return 任务,因此您要么必须使用 .Wait()
或 .Result
来阻止它们,要么忘记它。因为如果您不能等待它们,在工作流执行期间就会发生坏事,因为其他活动可能会在使用任务的活动完成其工作之前开始。
开发 WF 运行时时,任务的整个概念还很年轻,因此 WF 运行时没有/不适合它们。
编辑 2:
示例实现(基于 this excellent official documentation)
你的 activity 几乎是空的:
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);
我刚在这里看到你的相关问题:How to write a long running activity to call web services in WF 4.0
另一种方法是将您的 activity 实现为 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;
}
}
}
工作流实例保留在内存中,但至少工作流运行时可以处理其正常的调度任务,而其线程之一不会被较长的 运行 进程占用。
我创建了一个 activity 来执行网络请求并将结果存储到数据库中。通常这个过程大约需要 1 小时,它会使工作流引擎运行异常。我发现对于这些长时间的 运行 活动,我应该编写一些不同的代码,这样工作流引擎线程就不会被阻塞。
研究了一些关于写长运行活动的博客,我明白我应该使用Bookmark
概念。但是我没有使用 TPL 和 Task
.
此代码使用 Task
s 处理长 运行 activity 是否正确?
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;
}
}
}
不,这不是使用书签的方式。当工作流必须等待来自外部进程的输入时,将使用书签。
例如:我有一个文档审批工作流程,在某个时间点,工作流程必须等待人工审阅者对文档给予确定。当 ResumeBookmark
被调用时,工作流将被空闲并再次激活,而不是将工作流实例保存在内存中。
在您的情况下,您的工作流不能闲置,因为它在其上下文中有一个操作 运行。该操作是您的任务,顺便说一下,这是一个即发即弃的任务,因此 WF 无法处理严重故障。
现在,对于一个可能的解决方案,您可以考虑让另一个进程调用 GetAndSave
方法,并让该进程最终调用 WF 上的 ResumeBookmark
,这样工作流就可以被运行。该进程甚至可以是承载您的工作流程的同一进程。
有关示例,请参见 this blogpost。想象一下,您无需等待人在控制台中输入内容,而是执行了漫长的 运行 任务。
您没有指定 activity 之后的内容,但请注意,恢复书签后可以将 return 数据返回到工作流程。因此 GetAndSave
的任何结果,即使它只是一个错误代码,您都可以使用它来决定如何进一步处理工作流中的其他活动。
希望这对您有意义,并且您会看到我尝试概述的可能的解决方案。
编辑
关于在 WF 中使用任务或 async/await 的快速说明。据我所知,没有方法可以覆盖 return 任务,因此您要么必须使用 .Wait()
或 .Result
来阻止它们,要么忘记它。因为如果您不能等待它们,在工作流执行期间就会发生坏事,因为其他活动可能会在使用任务的活动完成其工作之前开始。
开发 WF 运行时时,任务的整个概念还很年轻,因此 WF 运行时没有/不适合它们。
编辑 2: 示例实现(基于 this excellent official documentation)
你的 activity 几乎是空的:
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);
我刚在这里看到你的相关问题:How to write a long running activity to call web services in WF 4.0
另一种方法是将您的 activity 实现为 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;
}
}
}
工作流实例保留在内存中,但至少工作流运行时可以处理其正常的调度任务,而其线程之一不会被较长的 运行 进程占用。