How to write a long running activity to call web services in WF 4.0

蹲街弑〆低调 提交于 2019-12-10 11:31:31

问题


I created an activity which executes a web request and stores the result into the database. I found out that for these long running activities I should write some different code so that the workflow engine thread won't be blocked.

public sealed class WebSaveActivity : NativeActivity
{
    protected override void Execute(NativeActivityContext context)
    {
       GetAndSave(); // This takes 1 hour to accomplish.
    }
}

How should I rewrite this activity to meet the requirements for a long running activity


回答1:


You could either spawn a thread within your existing process using e.g. ThreadPool.QueueUserWorkItem() so the rest of your workflow will continue to run if that is desired. Be sure to understand first what multithreading and thread synchronization means, though. Or you could look into Hangfire or similar components to offload the entire job into a different process.

EDIT:

Based on your comment you could look into Task-based Asynchronous Pattern (TAP): Link 1, Link 2 which would give you a nice model of writing code that continues to work on things that can be done while waiting for the result of your long running action until it returns. I am, however, not certain if this covers your all needs. In Windows Workflow Foundation specifically, you might want to look into some form of workflow hibernation/persistence.




回答2:


This scenario is where using WF's persistence feature shines. It allows you to persist a workflow instance to a database, to allow for some long running operation to complete. Once that completes, a second thread or process can re-hydrate the workflow instance and allow it to resume.

First you specify to the workflow application a workflow instance store. Microsoft provides a SQL workflow instance store implementation you can use, and provides the SQL scripts you can run on your SQL Server.

namespace MySolution.MyWorkflowApp
{
    using System.Activities;
    using System.Activities.DurableInstancing;
    using System.Activities.Statements;
    using System.Threading;

    internal static class Program
    {
        internal static void Main(string[] args)
        {
            var autoResetEvent = new AutoResetEvent(false);
            var workflowApp = new WorkflowApplication(new Sequence());
            workflowApp.InstanceStore = new SqlWorkflowInstanceStore("server=mySqlServer;initial catalog=myWfDb;...");
            workflowApp.Completed += e => autoResetEvent.Set();
            workflowApp.Unloaded += e => autoResetEvent.Set();
            workflowApp.Aborted += e => autoResetEvent.Set();
            workflowApp.Run();
            autoResetEvent.WaitOne();
        }
    }
}

Your activity would spin up a secondary process / thread that will actually perform the save operation. There is a variety of ways you could do this:

  • On a secondary thread
  • By invoking a web method asynchronously that actually does the heavy lifting of performing the save operation

Your activity would look like this:

public sealed class WebSaveActivity : NativeActivity
{
    public InArgument<MyBigObject> ObjectToSave { get; set; }

    protected override bool CanInduceIdle
    {
        get
        {
            // This notifies the WF engine that the activity can be unloaded / persisted to an instance store.
            return true;
        }
    }

    protected override void Execute(NativeActivityContext context)
    {
        var currentBigObject = this.ObjectToSave.Get(context);
        currentBigObject.WorkflowInstanceId = context.WorkflowInstanceId;
        StartSaveOperationAsync(this.ObjectToSave.Get(context)); // This method should offload the actual save process to a thread or even a web method, then return immediately.

        // This tells the WF engine that the workflow instance can be suspended and persisted to the instance store.
        context.CreateBookmark("MySaveOperation", AfterSaveCompletesCallback);
    }

    private void AfterSaveCompletesCallback(NativeActivityContext context, Bookmark bookmark, object value)
    {
        // Do more things after the save completes.
        var saved = (bool) value;
        if (saved)
        {
            // yay!
        }
        else
        {
            // boo!!!
        }
    }
}

The bookmark creation signals to the WF engine that the workflow instance can be unloaded from memory until something wakes up the workflow instance.

In your scenario, you'd like the workflow to resume once the long save operation completes. Lets assume the StartSaveOperationAsync method writes a small message to a queue of some sort, that a second thread or process polls to perform the save operations:

public static void StartSaveOperationAsync(MyBigObject myObjectToSave)
{
    var targetQueue = new MessageQueue(".\private$\pendingSaveOperations");
    var message = new Message(myObjectToSave);
    targetQueue.Send(message);
}

In my second process, I can then poll the queue for new save requests and re-hydrate the persisted workflow instance so it can resume after the save operation finishes. Assume that the following method is in a different console application:

internal static void PollQueue()
{
    var targetQueue = new MessageQueue(@".\private$\pendingSaveOperations");
    while (true)
    {
        // This waits for a message to arrive on the queue.
        var message = targetQueue.Receive();
        var myObjectToSave = message.Body as MyBigObject;

        // Perform the long running save operation
        LongRunningSave(myObjectToSave);

        // Once the save operation finishes, you can resume the associated workflow.
        var autoResetEvent = new AutoResetEvent(false);
        var workflowApp = new WorkflowApplication(new Sequence());
        workflowApp.InstanceStore = new SqlWorkflowInstanceStore("server=mySqlServer;initial catalog=myWfDb;...");
        workflowApp.Completed += e => autoResetEvent.Set();
        workflowApp.Unloaded += e => autoResetEvent.Set();
        workflowApp.Aborted += e => autoResetEvent.Set();

        // I'm assuming the object to save has a field somewhere that refers the workflow instance that's running it.
        workflowApp.Load(myObjectToSave.WorkflowInstanceId);
        workflowApp.ResumeBookmark("LongSaveOperation", true); // The 'true' parameter is just our way of saying the save completed successfully. You can use any object type you desire here.
        autoResetEvent.WaitOne();
    }
}

private static void LongRunningSave(object myObjectToSave)
{
    throw new NotImplementedException();
}

public class MyBigObject 
{
    public Guid WorkflowInstanceId { get; set; } = Guid.NewGuid();
}

Now the long running save operation will not impede the workflow engine, and it'll make more efficient use of system resources by not keeping workflow instances in memory for long periods of time.



来源:https://stackoverflow.com/questions/44619188/how-to-write-a-long-running-activity-to-call-web-services-in-wf-4-0

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!