Esempio n. 1
0
        // This program runs multiple threads:

        // RunnableInstancesDetectionThread
        //      This thread periodically polls the database and waits for workflow instances
        //      in the "runnable" state. When that happens, it raises an event and pauses
        //      until runnable instances have been processed (by another thread).
        //
        // ResumptionBookmarkThread
        //      This is the user interface thread using console input for a user to type commands.
        //      It implements a REPL (read-eval-print-loop) environment where a user can issue
        //      bookmark resumption commands. When this happens, the thread raises an event
        //      for bookmarks to be processed (by another thread).
        //      Valid commands have the following format:
        //      <app-name>[ <instance-id>]
        //
        // ProcessingThread
        //      This thread waits for events raised from other sources.
        //      Valid events are:
        //      - HasRunnableInstances
        //      - HasBookmark
        //
        //      When "HasBookmark" is raised, the thread resumes the
        //      corresponding in-memory workflow instance.
        //
        //      When "HasRunnableInstance" is raised, the thread attempts
        //      to call the "LoadRunnableInstance" method to load the
        //      corresponding workflow instance from the database.
        //      This must be done until the "LoadRunnableInstance" method
        //      raises and "InstanceNotReadyException". This signals that
        //      all runnable instances from the database have been processed.
        //      At this stage, the thread raises an event to resume the
        //      RunnableInstancesDetectionThread.

        // CHALLENGES:
        //
        //      In order to be able to resume a persisted runnable workflow instance
        //      it is necessary to know the workflow identity in order to match it
        //      with the corresponding XAML definition. This sample associates a
        //      unique identity for each XAML definition that is automatically
        //      persisted to the database and can be retrieved on the "HasRunnableInstance" event.
        //
        //      This sample persists and unloads workflow instances when bookmarked.
        //      Therefore, upon resumption, the a new instance is created with the corresponding
        //      XAML definition and the workflow execution is resumed.
        //      In crosscut, we try to prevent workflow instances from being unloaded if another
        //      message is received in a short period of time. Try to prevent workflow instances
        //      from being unloaded with this sample first.

        static void Main(string[] args)
        {
            var cmdLine   = CommandLine.Parse(args);
            var crash     = cmdLine.Crash;
            var operation = cmdLine.Operation;

            string bookmarkName = null;
            string instanceId   = null;

            var hasRunnableInstances = new ManualResetEvent(false);
            var stopping             = new ManualResetEvent(false);

            var monitorRunnableInstances = new AutoResetEvent(false);
            var hasBookmark = new AutoResetEvent(false);

            var store = CreateInstanceStore(out var handle);

            try
            {
                // RunnableInstancesDetectionThread
                //      This thread periodically polls the database and waits for workflow instances
                //      in the "runnable" state. When that happens, it raises an event and pauses
                //      until runnable instances have been processed (by another thread).
                //
                var runnableInstancesDetectionThread = new Thread(unused =>
                {
                    while (true)
                    {
                        var status = WaitHandle.WaitAny(new WaitHandle[]
                        {
                            stopping,
                            monitorRunnableInstances,
                        });

                        if (status == 0)
                        {
                            break;
                        }

                        if (status == 1)
                        {
                            var succeeded = WaitForRunnableInstance(store, handle, TimeSpan.FromSeconds(15.0));
                            if (succeeded)
                            {
                                hasRunnableInstances.Set();
                            }
                            else
                            {
                                monitorRunnableInstances.Set();
                            }
                        }
                    }
                });

                // ResumptionBookmarkThread
                //      This is the user interface thread using console input for a user to type commands.
                //      It implements a REPL (read-eval-print-loop) environment where a user can issue
                //      bookmark resumption commands. When this happens, the thread raises an event
                //      for bookmarks to be processed (by another thread).
                //      Valid commands have the following format:
                //      <app-name>[ <instance-id>]
                //
                var resumptionBookmarkThread = new Thread(unused =>
                {
                    while (true)
                    {
                        Console.WriteLine();
                        Console.WriteLine("Please, press ENTER to exit or type a bookmark name and ENTER to resume the specified bookmark.");

                        var line = Console.ReadLine();
                        if (line == "")
                        {
                            stopping.Set();
                            break;
                        }
                        else
                        {
                            var match = BookmarkRegex.Match(line);
                            if (match.Success)
                            {
                                if (match.Groups["instance"] != null)
                                {
                                    instanceId = match.Groups["instance"].Value;
                                }
                                bookmarkName = match.Groups["app"].Value;
                                hasBookmark.Set();
                            }
                            else
                            {
                                Console.Error.WriteLine("Invalid syntax.");
                            }
                        }
                    }
                });

                runnableInstancesDetectionThread.IsBackground = true;
                runnableInstancesDetectionThread.Start();

                resumptionBookmarkThread.Start();

                monitorRunnableInstances.Set();

                var instances = new Guid[2] {
                    Guid.Empty, Guid.Empty,
                };

                // The application starts a instance for two different workflow types
                // Instances are kept in memory for the duration of their lifetime.

                if (operation == CommandLine.Operations.Run)
                {
                    Console.WriteLine("Running instances of two difference workflow apps...");

                    instances[0] = RunWorkflow(store, WorkflowApps.App1);
                    instances[1] = RunWorkflow(store, WorkflowApps.App2);
                }

                // ProcessingThread
                //      This thread waits for events raised from other sources.
                //      Valid events are:
                //      - HasRunnableInstances
                //      - HasBookmark
                //
                //      When "HasBookmark" is raised, the thread resumes the
                //      corresponding in-memory workflow instance.
                //
                //      When "HasRunnableInstance" is raised, the thread attempts
                //      to call the "LoadRunnableInstance" method to load the
                //      corresponding workflow instance from the database.
                //      This must be done until the "LoadRunnableInstance" method
                //      raises and "InstanceNotReadyException". This signals that
                //      all runnable instances from the database have been processed.
                //      At this stage, the thread raises an event to resume the
                //      RunnableInstancesDetectionThread.

                while (true)
                {
                    var status = WaitHandle.WaitAny(new WaitHandle[]
                    {
                        stopping,
                        hasRunnableInstances,
                        hasBookmark,
                    });

                    if (status == WaitHandle.WaitTimeout)
                    {
                        throw new TimeoutException();
                    }

                    // stopping
                    if (status == 0)
                    {
                        break;
                    }

                    // hasRunnableInstances
                    if (status == 1)
                    {
                        try
                        {
                            var instance = WorkflowApplication.GetRunnableInstance(store);
                            if (crash)
                            {
                                throw new ApplicationException("CRASHED");
                            }
                            LoadRunnableInstance(store, instance);
                        }
                        catch (InstanceNotReadyException)
                        {
                            // no more runnable instances

                            Console.Error.WriteLine("Resuming runnable instances detection.");

                            hasRunnableInstances.Reset();
                            monitorRunnableInstances.Set();
                        }
                    }

                    // hasBookmark
                    if (status == 2)
                    {
                        System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(bookmarkName));
                        Console.WriteLine($"Resuming workflow with bookmark: \"{bookmarkName}\".");

                        var identifier = Guid.Empty;

                        if (String.IsNullOrEmpty(instanceId))
                        {
                            var index = 0;
                            if (bookmarkName == "App2")
                            {
                                index = 1;
                            }
                            identifier = instances[index];
                        }
                        else
                        {
                            identifier = Guid.Parse(instanceId);
                        }

                        ResumeWorkflow(store, identifier, bookmarkName);
                    }
                }

                Console.WriteLine("Done.");

                resumptionBookmarkThread.Join();
                runnableInstancesDetectionThread.Join();
            }
            finally
            {
            }
        }