You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何升级基于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:

1. Define the Async Interface

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();
}
2. Key Implementation Details

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;
    }
}
3. Usage Example

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
}
4. Backward Compatibility (Optional)

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:02:56