Hintergrund-Arbeitsaufgaben

Ab und an gibt es den Wunsch, Arbeitsaufgaben zu definieren die ohne Interaktion im Hintergrund ausgeführt werden.

Für diese Art von Aufgaben gibt es mit Version der 4.3.0 das neue Interface PiB.ProcessModel.Workflow.IWorkitemWithBackgroundExcecution.

Die Verwendung ist vorwiegend im Zusammenspiel mit dem cardo.Agent gedacht. Für das Verständnis des unten aufgeführten code-Beispiels ist das Verständnis der Arbeitsweise des cardo Agent Voraussetzung.

Das Interface implementiert z.Z. die folgende drei Methoden (ausführliche Dokumentation ist via Intellisense verfügbar).

  • bool EnableAutoStart();

    Wenn true, dann wird beim "Berechnen" des Prozessstatus die Aufgabe automatisch gestartet.

  • bool EnableAutoFinish();

    Wenn true, dann wird beim "Berechnen" des Prozessstatus die Aufgabe automatisch beendet.

  • IduIT.cardo.Agent.Shared.AgentJobState? GetAsyncJobState();

    Ruft den aktuellen Background-Status dieser Arbeitsaufgabe ab. Es kann auch null zurückgegeben werden.

Funktionsfähige Beispielimplementierung

namespace IduIT.App.SamplePibApp
{
	using RemoteJob = cardo.Core.Api.Agent.AgentJobWrapper<RemoteStuff.SampleRemoteWorkitem, RemoteStuff.SampleRemoteWorkitemConfig, string>;

	///<summary>
	/// "Transaktionen" für den Remote-Job
	///</summary>
	internal sealed class SimpleJobTransaction : IDU.DB.ITransactional
	{
		public enum Do
		{
			RemoveInRollback,
			RemoveInCommit
		}

		public SimpleJobTransaction(RemoteJob job, Do what)
		{
			_job = job;
			_what = what;
		}

		public void BeginTransaction() { }

		public bool TransactionInProgress => _job != null;

		public void Commit()
		{
			if (_what == Do.RemoveInCommit)
			{
				try { _job.RemoveJob(); }
				catch { /*nix ...*/ }
			}

			_job.Dispose();
			_job = null;
		}

		public void Dispose()
		{
			if (_job != null && _what == Do.RemoveInRollback)
			{
				try { _job.RemoveJob(); }
				catch { /*nix ...*/ }
			}

			_job.Dispose();
		}

		private RemoteJob _job;
		private readonly Do _what;
	}

	namespace RemoteStuff
	{
		internal sealed class SampleRemoteWorkitemConfig
		{
			public long WorkitemId { get; set; }
			public int Samples { get; set; }
		}

		///<summary>
		/// Implementierung der Arbeitsaufgabe, die auf dem cardo.Agent ausgeführt wird.
		///</summary>
		internal class SampleRemoteWorkitem : cardo.Core.Api.Applications.ApplicationAccessor<SampleApplication>,
			IduIT.cardo.Agent.Shared.IRemoteAsyncWorker<SampleRemoteWorkitemConfig, string>
		{
			public void Configure(SampleRemoteWorkitemConfig config)
			{
				_config = config;
			}

			public bool Run(cardo.Agent.Shared.ITaskRunState state)
			{
				//das Workitem, wie in der Config angegeben
				var wi = App.PibHdl.Workflow.GetWorkitemByWorkitemId(_config.WorkitemId);
				this._result = wi.Title;

				App.PibHdl.Commit();

				for (_current = 1; _current <= _config.Samples && !state.Cancellation.IsCancellationRequested; _current++)
				{
					System.Threading.Thread.Sleep(200);
				}

				return _current == _config.Samples;
			}

			public cardo.Agent.Shared.WorkerProgress GetProgress()
			{
				return new cardo.Agent.Shared.WorkerProgress()
				{
					Current = _current,
					Max = _config.Samples,
					Message = "Arbeite"
				};
			}

			public string GetResult()
			{
				return _result;
			}


			public void Dispose()
			{
			}


			public bool ResultContainsSensitiveData => true;

			private SampleRemoteWorkitemConfig _config;
			private string _result;
			private int _current;

		}
	}

	[PiB.ProcessModel.WorkitemDescription(
		"Demo-Arbeitsaufgabe für IWorkitemWithBackgroundExcecution",
		"",
		IsNonInteractive = true,
		EBAllowDelete = PiB.Tools.EnvDependendBool.True,
		EBAllowReopen = PiB.Tools.EnvDependendBool.True,
		IconCls = "fal:flask-gear")]
	internal sealed class AutoRunWorkitem : PiB.ProcessModel.WorkitemWithConfigBase<WorkitemConfiguration>,
		PiB.ProcessModel.Workflow.IWorkitemInteraction,
		PiB.ProcessModel.Workflow.IWorkitemInteraction2,
		PiB.ProcessModel.Workflow.IWorkitemWithBackgroundExcecution
	{
		public AutoRunWorkitem() { }

		public AutoRunWorkitem(string title)
		{
			Title = title;
		}

		protected override string ToHTML(Func<PiB.Presentation.TemplateEngine.ScribanConfiguration> scribanConfigFactory)
		{
			var result = base.ToHTML(scribanConfigFactory);

			var state = GetAsyncJobState();
			if (state != null)
				result += "<pre>" + state.ToString().HtmlEncode().Replace(",", "<br/>") + "</pre>";

			return result;
		}



		#region IWorkitemInteraction

		void PiB.ProcessModel.Workflow.IWorkitemInteraction.OnStart(PiB.ProcessModel.WorkitemTreeWrapper tree, PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx)
		{
			//Hier direkt den Remote-Job auf dem Agent erstellen
			//Die Aktion wird direkt im CTOR ausgelöst ...
			var remoteJob = new RemoteJob(
				title: this.Title.QuoteString() + " - Aufgabe, die wartet...",
				config: new()
				{
					WorkitemId = this.WorkitemId,
					Samples = 200
				}
			);

			//wenn wir hier sind, dann ist auch die Guid da.
			//Im Falle eines Rollback der PiB-Aktion
			//hier den Job wieder löschen (bzw. zum Löschen markieren)

			ctx.Handler.PibHandler.AddTransactional(
				new SimpleJobTransaction(remoteJob, SimpleJobTransaction.Do.RemoveInRollback),
				takeOwnership: true, where: PiB.PibHandler.Pos.End);

			//die Guid in der Konfig dieser Arbeitsaufgabe speichern ...

			if (this.Config == null)
				this.Config = new WorkitemConfiguration();

			this.Config.Set("remoteJobGuid", remoteJob.JobGuid.Value.ToString());
		}

		void PiB.ProcessModel.Workflow.IWorkitemInteraction.OnReOpen(PiB.ProcessModel.WorkitemTreeWrapper tree, PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx)
		{

		}

		void PiB.ProcessModel.Workflow.IWorkitemInteraction.OnFinish(PiB.ProcessModel.WorkitemTreeWrapper tree, PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx)
		{
			var guid = TryJobGuid().Value;

			//Instanz des Jobs mit der Guid, oben mit .Value 
			//hätte es sonst bereits einen Fehler gegeben.
			var remoteJob = new RemoteJob(guid);

			//Prüfen, ob ein Ergebnis vorliegen
			//Das Remove des Jobs erst im Commit der Pib-Transaktion
			if (!remoteJob.TryGetResult(out var result, removeOnSuccess: false))
			{
				remoteJob.Dispose();
				throw new Exception("Bin noch nicht fertig! " + remoteJob.JobState?.Progress?.ToString());
			}

			//wenn bis hier, dann war der Datenabruf erfolgreich, die Aufgabe 
			//ist also beendet, damit im Falle eines Rollbacks der Job noch auf dem Server
			//verfügbar ist, erst im Commit das RemoveJob auslösen...

			ctx.Handler.PibHandler.AddTransactional(
				new SimpleJobTransaction(remoteJob, SimpleJobTransaction.Do.RemoveInCommit),
				takeOwnership: true, where: PiB.PibHandler.Pos.End);

			//Die Guid dann aus der Konfig löschen
			this.Config.Set("remoteJobGuid", null);

		}

		void PiB.ProcessModel.Workflow.IWorkitemInteraction.OnFinished(PiB.ProcessModel.WorkitemTreeWrapper tree, PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx)
		{

		}

		#endregion

		#region IWorkitemWithBackgroundExcecution

		bool PiB.ProcessModel.Workflow.IWorkitemWithBackgroundExcecution.EnableAutoStart()
		{
			return true;
		}

		bool PiB.ProcessModel.Workflow.IWorkitemWithBackgroundExcecution.EnableAutoFinish()
		{
			return true;
		}

		IduIT.cardo.Agent.Shared.AgentJobState PiB.ProcessModel.Workflow.IWorkitemWithBackgroundExcecution.GetAsyncJobState()
		{
			return this.GetAsyncJobState();

		}

		#endregion

		#region IWorkitemInteraction2

		PiB.ProcessModel.Workflow.Result PiB.ProcessModel.Workflow.IWorkitemInteraction2.OnBeforeFinish(PiB.ProcessModel.WorkitemTreeWrapper tree, PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx, Action callMeBefordeModifyTree)
		{
			return PiB.ProcessModel.Workflow.Result.Continue;
		}

		void PiB.ProcessModel.Workflow.IWorkitemInteraction2.OnDelete(PiB.ProcessModel.Workflow.IWorkflowInteractionInformationContext ctx)
		{
			var guid = TryJobGuid();
			if (guid.HasValue)
			{
				try
				{
					ctx.Handler.PibHandler.AddTransactional(new SimpleJobTransaction(
						new RemoteJob(guid.Value), SimpleJobTransaction.Do.RemoveInCommit),
						takeOwnership: true, where: PiB.PibHandler.Pos.End);
				}
				catch
				{
					//nix
				}
			}
		}
		#endregion

		private cardo.Agent.Shared.AgentJobState GetAsyncJobState()
		{
			var guid = TryJobGuid();
			if (guid.HasValue)
			{
				using var remoteJob = new RemoteJob(guid.Value);
				return remoteJob.JobState;
			}
			return null;
		}

		private Guid? TryJobGuid()
		{
			var str = this.Config?.TryGet("remoteJobGuid")?.ToString();
			return str != null ? Guid.Parse(str) : null;
		}
	}
}


Zuletzt geändert: 22.04.2024 18:22:09 (erstmals erstellt 01.01.2024)