DragAndDrop core
This commit is contained in:
829
Lattice.Core.DragDrop/Services/DragDropService.cs
Normal file
829
Lattice.Core.DragDrop/Services/DragDropService.cs
Normal file
@@ -0,0 +1,829 @@
|
||||
namespace Lattice.Core.DragDrop.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Реализация сервиса управления операциями перетаскивания.
|
||||
/// Полностью потокобезопасная реализация с поддержкой async/await.
|
||||
/// </summary>
|
||||
public sealed class DragDropService : IDragDropService
|
||||
{
|
||||
#region Nested Types
|
||||
|
||||
private sealed class DropTargetInfo : IDisposable
|
||||
{
|
||||
public required Abstractions.IDropTarget Target { get; init; }
|
||||
public required Geometry.Rect Bounds { get; set; }
|
||||
public required int Priority { get; init; }
|
||||
public required string? Group { get; init; }
|
||||
public required string Id { get; init; }
|
||||
public DateTime LastAccessTime { get; set; } = DateTime.UtcNow;
|
||||
public int UsageCount { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Target is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DragOperationContext : IDisposable
|
||||
{
|
||||
public Abstractions.IDragSource? Source { get; set; }
|
||||
public Models.DragInfo? DragInfo { get; set; }
|
||||
public Abstractions.IDropTarget? CurrentDropTarget { get; set; }
|
||||
public CancellationTokenSource? CancellationTokenSource { get; set; }
|
||||
public DateTime StartTime { get; set; } = DateTime.UtcNow;
|
||||
public bool ThresholdExceeded { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DragInfo?.Dispose();
|
||||
CancellationTokenSource?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private readonly Dictionary<string, DropTargetInfo> _dropTargets = new();
|
||||
private readonly ReaderWriterLockSlim _dropTargetsLock = new(LockRecursionPolicy.NoRecursion);
|
||||
private readonly object _dragOperationLock = new();
|
||||
|
||||
private DragOperationContext? _currentDragOperation;
|
||||
private Timer? _cleanupTimer;
|
||||
private bool _disposed;
|
||||
|
||||
private int _totalDragOperations;
|
||||
private int _successfulDrops;
|
||||
private int _cancelledOperations;
|
||||
private int _errorCount;
|
||||
private long _totalOperationTicks;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
private event EventHandler<DragStartedEventArgs>? _dragStarted;
|
||||
private event EventHandler<DragUpdatedEventArgs>? _dragUpdated;
|
||||
private event EventHandler<DropTargetChangedEventArgs>? _dropTargetChanged;
|
||||
private event EventHandler<DragCompletedEventArgs>? _dragCompleted;
|
||||
private event EventHandler<DragCancelledEventArgs>? _dragCancelled;
|
||||
private event EventHandler<DragDropErrorEventArgs>? _errorOccurred;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public bool IsDragActive => Volatile.Read(ref _currentDragOperation) != null;
|
||||
|
||||
public Models.DragInfo? CurrentDragInfo => _currentDragOperation?.DragInfo;
|
||||
|
||||
public Abstractions.IDropTarget? CurrentDropTarget => _currentDragOperation?.CurrentDropTarget;
|
||||
|
||||
public double DragStartThreshold { get; set; } = 3.0;
|
||||
|
||||
public bool EnableAsyncOperations { get; set; } = true;
|
||||
|
||||
public int AsyncOperationTimeout { get; set; } = 5000;
|
||||
|
||||
public event EventHandler<DragStartedEventArgs> DragStarted
|
||||
{
|
||||
add => _dragStarted += value;
|
||||
remove => _dragStarted -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<DragUpdatedEventArgs> DragUpdated
|
||||
{
|
||||
add => _dragUpdated += value;
|
||||
remove => _dragUpdated -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<DropTargetChangedEventArgs> DropTargetChanged
|
||||
{
|
||||
add => _dropTargetChanged += value;
|
||||
remove => _dropTargetChanged -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<DragCompletedEventArgs> DragCompleted
|
||||
{
|
||||
add => _dragCompleted += value;
|
||||
remove => _dragCompleted -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<DragCancelledEventArgs> DragCancelled
|
||||
{
|
||||
add => _dragCancelled += value;
|
||||
remove => _dragCancelled -= value;
|
||||
}
|
||||
|
||||
public event EventHandler<DragDropErrorEventArgs> ErrorOccurred
|
||||
{
|
||||
add => _errorOccurred += value;
|
||||
remove => _errorOccurred -= value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public DragDropService()
|
||||
{
|
||||
// Инициализация таймера очистки (каждые 5 минут)
|
||||
_cleanupTimer = new Timer(CleanupExpiredTargets, null,
|
||||
TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Registration Methods
|
||||
|
||||
public string RegisterDropTarget(Abstractions.IDropTarget target, Geometry.Rect bounds, int priority = 0, string? group = null)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||
|
||||
var id = Guid.NewGuid().ToString("N");
|
||||
var info = new DropTargetInfo
|
||||
{
|
||||
Target = target,
|
||||
Bounds = bounds,
|
||||
Priority = priority,
|
||||
Group = group,
|
||||
Id = id
|
||||
};
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
_dropTargets[id] = info;
|
||||
return id;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateDropTargetBounds(string id, Geometry.Rect bounds)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_dropTargetsLock.EnterUpgradeableReadLock();
|
||||
try
|
||||
{
|
||||
if (!_dropTargets.TryGetValue(id, out var info))
|
||||
return false;
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
info.Bounds = bounds;
|
||||
info.LastAccessTime = DateTime.UtcNow;
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitUpgradeableReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UnregisterDropTarget(string id)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_dropTargets.Remove(id, out var info))
|
||||
{
|
||||
info.Dispose();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterDropTargetsInGroup(string group)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (string.IsNullOrEmpty(group)) return;
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
var idsToRemove = new List<string>();
|
||||
|
||||
foreach (var kvp in _dropTargets)
|
||||
{
|
||||
if (kvp.Value.Group == group)
|
||||
{
|
||||
idsToRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in idsToRemove)
|
||||
{
|
||||
if (_dropTargets.Remove(id, out var info))
|
||||
{
|
||||
info.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Async Operations
|
||||
|
||||
public async Task<bool> StartDragAsync(Abstractions.IDragSource source, Geometry.Point startPosition)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
if (_currentDragOperation != null)
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Interlocked.Increment(ref _totalDragOperations);
|
||||
|
||||
Models.DragInfo? dragInfo = null;
|
||||
|
||||
// Проверка возможности начала перетаскивания
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
|
||||
{
|
||||
var result = await ExecuteWithTimeoutAsync(
|
||||
asyncSource.CanStartDragAsync(),
|
||||
"CanStartDragAsync",
|
||||
source);
|
||||
|
||||
if (!result.CanStart || result.DragInfo == null)
|
||||
return false;
|
||||
|
||||
dragInfo = result.DragInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!source.CanStartDrag(out dragInfo) || dragInfo == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
var updatedDragInfo = dragInfo.CloneWithPosition(startPosition);
|
||||
|
||||
// Начало перетаскивания
|
||||
bool started;
|
||||
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource2)
|
||||
{
|
||||
started = await ExecuteWithTimeoutAsync(
|
||||
asyncSource2.StartDragAsync(updatedDragInfo),
|
||||
"StartDragAsync",
|
||||
source);
|
||||
}
|
||||
else
|
||||
{
|
||||
started = source.StartDrag(updatedDragInfo);
|
||||
}
|
||||
|
||||
if (!started)
|
||||
{
|
||||
updatedDragInfo.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
_currentDragOperation = new DragOperationContext
|
||||
{
|
||||
Source = source,
|
||||
DragInfo = updatedDragInfo,
|
||||
CancellationTokenSource = new CancellationTokenSource()
|
||||
};
|
||||
}
|
||||
|
||||
// Вызов события
|
||||
_dragStarted?.Invoke(this, new DragStartedEventArgs(updatedDragInfo, startPosition));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, "StartDragAsync", source);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDragAsync(Geometry.Point position)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
DragOperationContext? context;
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
context = _currentDragOperation;
|
||||
}
|
||||
|
||||
if (context?.DragInfo == null || context.Source == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Проверка порога начала
|
||||
if (!context.ThresholdExceeded && DragStartThreshold > 0)
|
||||
{
|
||||
var distance = CalculateDistance(context.DragInfo.StartPosition, position);
|
||||
if (distance < DragStartThreshold)
|
||||
return;
|
||||
|
||||
context.ThresholdExceeded = true;
|
||||
}
|
||||
|
||||
var updatedDragInfo = context.DragInfo.CloneWithPosition(position);
|
||||
context.DragInfo.Dispose();
|
||||
context.DragInfo = updatedDragInfo;
|
||||
|
||||
// Поиск новой цели сброса
|
||||
var newDropTarget = await FindDropTargetAsync(position, updatedDragInfo);
|
||||
|
||||
// Обработка смены цели
|
||||
if (context.CurrentDropTarget != newDropTarget?.Target)
|
||||
{
|
||||
if (context.CurrentDropTarget != null)
|
||||
{
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragLeaveAsync(),
|
||||
t => t.DragLeave(),
|
||||
"DragLeave");
|
||||
}
|
||||
|
||||
context.CurrentDropTarget = newDropTarget?.Target;
|
||||
|
||||
if (newDropTarget != null)
|
||||
{
|
||||
newDropTarget.UsageCount++;
|
||||
_dropTargetChanged?.Invoke(this, new DropTargetChangedEventArgs(
|
||||
updatedDragInfo, newDropTarget.Target, newDropTarget.Bounds));
|
||||
}
|
||||
}
|
||||
|
||||
// Уведомление текущей цели
|
||||
if (context.CurrentDropTarget != null)
|
||||
{
|
||||
var dropInfo = new Models.DropInfo(
|
||||
updatedDragInfo.Data,
|
||||
position,
|
||||
updatedDragInfo.AllowedEffects,
|
||||
context.CurrentDropTarget);
|
||||
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DragOverAsync(dropInfo),
|
||||
t => t.DragOver(dropInfo),
|
||||
"DragOver");
|
||||
}
|
||||
|
||||
_dragUpdated?.Invoke(this, new DragUpdatedEventArgs(updatedDragInfo, position));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, "UpdateDragAsync", context);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Enums.DragDropEffects> EndDragAsync(Geometry.Point position)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
DragOperationContext? context;
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
context = _currentDragOperation;
|
||||
_currentDragOperation = null;
|
||||
}
|
||||
|
||||
if (context == null || context.DragInfo == null || context.Source == null)
|
||||
{
|
||||
Reset();
|
||||
return Enums.DragDropEffects.None;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var effects = Enums.DragDropEffects.None;
|
||||
var operationTime = DateTime.UtcNow - context.StartTime;
|
||||
Interlocked.Add(ref _totalOperationTicks, operationTime.Ticks);
|
||||
|
||||
// Выполнение сброса
|
||||
if (context.CurrentDropTarget != null)
|
||||
{
|
||||
var dropInfo = new Models.DropInfo(
|
||||
context.DragInfo.Data,
|
||||
position,
|
||||
context.DragInfo.AllowedEffects,
|
||||
context.CurrentDropTarget);
|
||||
|
||||
await ExecuteTargetOperationAsync(
|
||||
context.CurrentDropTarget,
|
||||
t => t.DropAsync(dropInfo),
|
||||
t => t.Drop(dropInfo),
|
||||
"Drop");
|
||||
|
||||
if (dropInfo.Handled)
|
||||
{
|
||||
effects = dropInfo.SuggestedEffects;
|
||||
Interlocked.Increment(ref _successfulDrops);
|
||||
}
|
||||
}
|
||||
|
||||
// Уведомление источника
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCompletedAsync(context.DragInfo, effects),
|
||||
s => s.DragCompleted(context.DragInfo, effects),
|
||||
"DragCompleted",
|
||||
effects);
|
||||
|
||||
// Событие завершения
|
||||
_dragCompleted?.Invoke(this, new DragCompletedEventArgs(
|
||||
context.DragInfo, position, effects));
|
||||
|
||||
return effects;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, "EndDragAsync", context);
|
||||
return Enums.DragDropEffects.None;
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CancelDragAsync()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
DragOperationContext? context;
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
context = _currentDragOperation;
|
||||
_currentDragOperation = null;
|
||||
}
|
||||
|
||||
if (context == null || context.DragInfo == null || context.Source == null)
|
||||
{
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
context.CancellationTokenSource?.Cancel();
|
||||
Interlocked.Increment(ref _cancelledOperations);
|
||||
|
||||
await ExecuteSourceOperationAsync(
|
||||
context.Source,
|
||||
s => s.DragCancelledAsync(context.DragInfo),
|
||||
s => s.DragCancelled(context.DragInfo),
|
||||
"DragCancelled");
|
||||
|
||||
_dragCancelled?.Invoke(this, new DragCancelledEventArgs(context.DragInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, "CancelDragAsync", context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Synchronous Operations (for compatibility)
|
||||
|
||||
public bool StartDrag(Abstractions.IDragSource source, Geometry.Point startPosition)
|
||||
{
|
||||
return Task.Run(() => StartDragAsync(source, startPosition)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void UpdateDrag(Geometry.Point position)
|
||||
{
|
||||
Task.Run(() => UpdateDragAsync(position)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Enums.DragDropEffects EndDrag(Geometry.Point position)
|
||||
{
|
||||
return Task.Run(() => EndDragAsync(position)).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public void CancelDrag()
|
||||
{
|
||||
Task.Run(CancelDragAsync).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Utility Methods
|
||||
|
||||
public void ClearAllDropTargets()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var info in _dropTargets.Values)
|
||||
{
|
||||
info.Dispose();
|
||||
}
|
||||
_dropTargets.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public DragDropStats GetStats()
|
||||
{
|
||||
return new DragDropStats
|
||||
{
|
||||
TotalDragOperations = _totalDragOperations,
|
||||
SuccessfulDrops = _successfulDrops,
|
||||
CancelledOperations = _cancelledOperations,
|
||||
ErrorCount = _errorCount,
|
||||
RegisteredTargets = _dropTargets.Count,
|
||||
AverageOperationTime = _totalDragOperations > 0
|
||||
? TimeSpan.FromTicks(_totalOperationTicks / _totalDragOperations)
|
||||
: TimeSpan.Zero
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
private async Task<DropTargetInfo?> FindDropTargetAsync(Geometry.Point position, Models.DragInfo dragInfo)
|
||||
{
|
||||
DropTargetInfo? bestTarget = null;
|
||||
|
||||
_dropTargetsLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
foreach (var info in _dropTargets.Values)
|
||||
{
|
||||
if (!info.Bounds.Contains(position))
|
||||
continue;
|
||||
|
||||
var dropInfo = new Models.DropInfo(
|
||||
dragInfo.Data,
|
||||
position,
|
||||
dragInfo.AllowedEffects,
|
||||
info.Target);
|
||||
|
||||
bool canAccept;
|
||||
|
||||
if (EnableAsyncOperations && info.Target is Abstractions.IAsyncDropTarget asyncTarget)
|
||||
{
|
||||
canAccept = await ExecuteWithTimeoutAsync(
|
||||
asyncTarget.CanAcceptDropAsync(dropInfo),
|
||||
"CanAcceptDropAsync",
|
||||
info.Target);
|
||||
}
|
||||
else
|
||||
{
|
||||
canAccept = info.Target.CanAcceptDrop(dropInfo);
|
||||
}
|
||||
|
||||
if (canAccept)
|
||||
{
|
||||
info.LastAccessTime = DateTime.UtcNow;
|
||||
|
||||
if (bestTarget == null || info.Priority > bestTarget.Priority)
|
||||
{
|
||||
bestTarget = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitReadLock();
|
||||
}
|
||||
|
||||
return bestTarget;
|
||||
}
|
||||
|
||||
private async Task ExecuteTargetOperationAsync(
|
||||
Abstractions.IDropTarget target,
|
||||
Func<Abstractions.IAsyncDropTarget, Task> asyncOperation,
|
||||
Action<Abstractions.IDropTarget> syncOperation,
|
||||
string operationName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && target is Abstractions.IAsyncDropTarget asyncTarget)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncTarget),
|
||||
$"{operationName}Async",
|
||||
target);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncOperation(target);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, operationName, target);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteSourceOperationAsync(
|
||||
Abstractions.IDragSource source,
|
||||
Func<Abstractions.IAsyncDragSource, Task> asyncOperation,
|
||||
Action<Abstractions.IDragSource> syncOperation,
|
||||
string operationName,
|
||||
Enums.DragDropEffects effects = Enums.DragDropEffects.None)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (EnableAsyncOperations && source is Abstractions.IAsyncDragSource asyncSource)
|
||||
{
|
||||
await ExecuteWithTimeoutAsync(
|
||||
asyncOperation(asyncSource),
|
||||
$"{operationName}Async",
|
||||
source);
|
||||
}
|
||||
else
|
||||
{
|
||||
syncOperation(source);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Increment(ref _errorCount);
|
||||
HandleError(ex, operationName, source);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T> ExecuteWithTimeoutAsync<T>(Task<T> task, string operationName, object? context = null)
|
||||
{
|
||||
if (AsyncOperationTimeout <= 0)
|
||||
return await task;
|
||||
|
||||
var timeoutTask = Task.Delay(AsyncOperationTimeout);
|
||||
var completedTask = await Task.WhenAny(task, timeoutTask);
|
||||
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms");
|
||||
}
|
||||
|
||||
return await task;
|
||||
}
|
||||
|
||||
private async Task ExecuteWithTimeoutAsync(Task task, string operationName, object? context = null)
|
||||
{
|
||||
if (AsyncOperationTimeout <= 0)
|
||||
{
|
||||
await task;
|
||||
return;
|
||||
}
|
||||
|
||||
var timeoutTask = Task.Delay(AsyncOperationTimeout);
|
||||
var completedTask = await Task.WhenAny(task, timeoutTask);
|
||||
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
throw new TimeoutException($"{operationName} timed out after {AsyncOperationTimeout}ms");
|
||||
}
|
||||
|
||||
await task;
|
||||
}
|
||||
|
||||
private double CalculateDistance(Geometry.Point p1, Geometry.Point p2)
|
||||
{
|
||||
var dx = p2.X - p1.X;
|
||||
var dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
_currentDragOperation?.Dispose();
|
||||
_currentDragOperation = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupExpiredTargets(object? state)
|
||||
{
|
||||
var expirationTime = DateTime.UtcNow.AddMinutes(-10); // Цели старше 10 минут
|
||||
|
||||
_dropTargetsLock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
var idsToRemove = new List<string>();
|
||||
|
||||
foreach (var kvp in _dropTargets)
|
||||
{
|
||||
if (kvp.Value.LastAccessTime < expirationTime)
|
||||
{
|
||||
idsToRemove.Add(kvp.Key);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in idsToRemove)
|
||||
{
|
||||
if (_dropTargets.Remove(id, out var info))
|
||||
{
|
||||
info.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dropTargetsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleError(Exception exception, string operation, object? context = null)
|
||||
{
|
||||
_errorOccurred?.Invoke(this, new DragDropErrorEventArgs(exception, operation, context));
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
lock (_dragOperationLock)
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
_cleanupTimer?.Dispose();
|
||||
_cleanupTimer = null;
|
||||
|
||||
if (_currentDragOperation != null)
|
||||
{
|
||||
_currentDragOperation.CancellationTokenSource?.Cancel();
|
||||
_currentDragOperation.Dispose();
|
||||
_currentDragOperation = null;
|
||||
}
|
||||
|
||||
ClearAllDropTargets();
|
||||
|
||||
_dropTargetsLock.Dispose();
|
||||
|
||||
// Очистка событий
|
||||
_dragStarted = null;
|
||||
_dragUpdated = null;
|
||||
_dropTargetChanged = null;
|
||||
_dragCompleted = null;
|
||||
_dragCancelled = null;
|
||||
_errorOccurred = null;
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user