//******************************************************************************************************
// TaskSynchronizedOperation.cs - Gbtc
//
// Copyright © 2021, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 02/26/2021 - Stephen C. Wills
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Threading.Tasks;
namespace GSF.Threading
{
///
/// Represents a task-based synchronized operation
/// that cannot run while it is already in progress.
///
///
/// The action performed by the is executed using
/// . Pending actions run when the task returned by the
/// asynchronous action is completed. This synchronized operation only supports the async
/// versions of the interface because the async action
/// cannot be executed synchronously.
///
public class TaskSynchronizedOperation : ISynchronizedOperation
{
#region [ Members ]
// Nested Types
private class SynchronizedOperation : SynchronizedOperationBase
{
private Func AsyncAction { get; }
private Action ExceptionHandler { get; }
public SynchronizedOperation(Func asyncAction, Action exceptionHandler)
: base(() => { })
{
if (asyncAction is null)
throw new ArgumentNullException(nameof(asyncAction));
AsyncAction = asyncAction;
ExceptionHandler = exceptionHandler;
}
protected override void ExecuteActionAsync()
{
_ = Task.Run(async () =>
{
try { await AsyncAction(); }
catch (Exception ex) { TryHandleException(ex); }
if (ExecuteAction())
ExecuteActionAsync();
});
}
private void TryHandleException(Exception ex)
{
try { ExceptionHandler?.Invoke(ex); }
catch { /* No other way to handle exceptions at this point */ }
}
}
#endregion
#region [ Constructors ]
///
/// Creates a new instance of the class.
///
/// The action to be performed during this operation.
public TaskSynchronizedOperation(Func asyncAction)
: this(asyncAction, null)
{
}
///
/// Creates a new instance of the class.
///
/// The action to be performed during this operation.
/// The action to be performed if an exception is thrown from the action.
public TaskSynchronizedOperation(Func asyncAction, Action exceptionAction) =>
InternalSynchronizedOperation = new SynchronizedOperation(asyncAction, exceptionAction);
#endregion
#region [ Properties ]
///
/// Gets a value to indicate whether the synchronized
/// operation is currently executing its action.
///
public bool IsRunning => InternalSynchronizedOperation.IsRunning;
///
/// Gets a value to indiate whether the synchronized operation
/// has an additional operation that is pending execution after
/// the currently running action has completed.
///
public bool IsPending => InternalSynchronizedOperation.IsPending;
private SynchronizedOperation InternalSynchronizedOperation { get; }
#endregion
#region [ Methods ]
///
/// Executes the action on another thread or marks the
/// operation as pending if the operation is already running.
///
///
/// When the operation is marked as pending, it will run again after the
/// operation that is currently running has completed. This is useful if
/// an update has invalidated the operation that is currently running and
/// will therefore need to be run again.
///
public void RunOnceAsync() =>
InternalSynchronizedOperation.RunOnceAsync();
///
/// Attempts to execute the action on another thread.
/// Does nothing if the operation is already running.
///
public void TryRunOnceAsync() =>
InternalSynchronizedOperation.TryRunOnceAsync();
void ISynchronizedOperation.Run() => RunOnceAsync();
void ISynchronizedOperation.TryRun() => TryRunOnceAsync();
void ISynchronizedOperation.RunOnce() => RunOnceAsync();
void ISynchronizedOperation.TryRunOnce() => TryRunOnceAsync();
#endregion
}
}