如何升级基于Action的UndoRedoTransactionManager以支持async/await?
Got it, let's walk through upgrading your IUndoRedoTransactionManager to fully support async/await. Here's a practical approach that maintains the core functionality while accommodating asynchronous operations:
First, we'll create an async-aware version of your interface. The key changes are adapting methods to work with Func<Task> instead of Action, and returning Task/Task<bool> to enable proper awaiting:
public interface IAsyncUndoRedoTransactionManager { // State checks remain synchronous (fast in-memory operations) bool CanDo { get; } bool CanUndo { get; } void ClearHistory(); // Async version of Do: accepts async do/undo actions Task<bool> Do(Func<Task> doAction, Func<Task> undoAction, string description); // Async execution methods for undo/redo Task<bool> Redo(); Task RedoAll(); Task<bool> Undo(); Task UndoAll(); }
When building the async manager, keep these considerations in mind:
Atomicity for Async Operations
Only add a transaction to history if the async doAction completes successfully. If it throws an exception, skip adding it to avoid invalid undo/redo states:
private readonly Stack<TransactionEntry> _undoHistory = new(); private readonly Stack<TransactionEntry> _redoHistory = new(); private class TransactionEntry { public Func<Task> DoFunc { get; } public Func<Task> UndoFunc { get; } public string Description { get; } public TransactionEntry(Func<Task> doFunc, Func<Task> undoFunc, string description) { DoFunc = doFunc; UndoFunc = undoFunc; Description = description; } } public async Task<bool> Do(Func<Task> doAction, Func<Task> undoAction, string description) { if (doAction == null || undoAction == null) throw new ArgumentNullException(nameof(doAction)); try { await doAction(); // Only record the transaction if it succeeded _undoHistory.Push(new TransactionEntry(doAction, undoAction, description)); _redoHistory.Clear(); // Reset redo stack after new action // Update state (raise an event here if you need to notify UI bindings) OnTransactionStateChanged(); return true; } catch (Exception ex) { // Log the exception as needed return false; } }
Thread Safety & UI Context
If your manager is used with UI code, ensure async operations return to the UI thread (this is the default behavior with await, so no extra code is needed unless you changed ConfigureAwait). Protect your history stacks with locks if the manager is accessed across multiple threads.
Undo/Redo Implementation
For async undo/redo, simply await the stored functions and manage the history stacks accordingly:
public async Task<bool> Undo() { if (!CanUndo) return false; var entry = _undoHistory.Pop(); try { await entry.UndoFunc(); _redoHistory.Push(entry); OnTransactionStateChanged(); return true; } catch { // Restore the entry if undo fails _undoHistory.Push(entry); return false; } } public async Task<bool> Redo() { if (!CanDo) return false; var entry = _redoHistory.Pop(); try { await entry.DoFunc(); _undoHistory.Push(entry); OnTransactionStateChanged(); return true; } catch { // Restore the entry if redo fails _redoHistory.Push(entry); return false; } }
Here's how your original code would look with the async manager:
var done = false; var success = await _asyncManager.Do( async () => { // Replace Thread.Sleep with Task.Delay for non-blocking waits await Task.Delay(100); // Run async UI work (ensure this executes on the UI thread if needed) await SomeUiWorkAsync(); done = true; }, async () => { await Task.Delay(100); await UndoSomeUiWorkAsync(); done = false; }, "Update application UI state"); if (success) { // Handle successful operation }
If you need to support existing synchronous code, add overloads to the async interface that wrap sync actions as async tasks:
public bool Do(Action doAction, Action undoAction, string description) { // Use GetAwaiter().GetResult() cautiously to avoid deadlocks in UI contexts return Do( () => { doAction(); return Task.CompletedTask; }, () => { undoAction(); return Task.CompletedTask; }, description).GetAwaiter().GetResult(); }
内容的提问来源于stack exchange,提问作者ManIkWeet

