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

长时任务触发更新事件引发的竞态条件问题排查与解决

问题根源分析

你的代码出现竞态条件并触发空引用异常,核心原因有两点:

  1. 变量拼写错误:OnUpdateEvent方法中误用了_curentlyRunningItem(少了一个'r'),实际类中定义的字段是_currentlyRunningItem,直接导致访问未绑定的变量;
  2. ConfigureAwait(false)的误用:该配置让await后的代码(finally块)在非UI线程执行,即便Dispatcher.Invoke是同步调用,多线程调度的不确定性仍可能导致finally块先于UI线程的事件处理完成,提前将字段设为null。

此外,Task.Factory.StartNew的默认调度器可能引发意外线程行为,建议改用Task.Run明确将长时任务放到线程池执行。


解决方案

方案一:修复拼写错误并移除ConfigureAwait(false)

这是最直接的修复方式,确保await后的代码回到UI线程执行,保证事件处理完成后再清空字段:

public class DemoViewModel
{
    private DemoRunClass DemoRunner;
    private ItemViewModel _currentlyRunningItem;

    public async Task RunNextItem()
    {
        var nextRunnableItemViewModel = GetNextItem();
        var runItemCommand = GenerateCommand(nextRunnableItemViewModel);

        _currentlyRunningItem = nextRunnableItemViewModel;

        try
        {
            // 移除ConfigureAwait(false),确保await后回到UI线程
            await DemoRunner.ExecuteLongRunningTask(runItemCommand, Dispatcher.CurrentDispatcher);
        }
        catch (Exception ex)
        { 
            // 异常处理逻辑
        }
        finally
        {
            _currentlyRunningItem = null;
        }
    }

    private void OnUpdateEvent(object status)
    {
        if (status == "Method1 complete")
            _currentlyRunningItem.OnMethod1Complete(); // 修正拼写错误
        // ...
        else if (status == "All Complete")
            _currentlyRunningItem.OnAllComplete(); // 修正拼写错误
    }
}

方案二:改用Task.Run执行长时任务

替换Task.Factory.StartNew为Task.Run,明确将任务放到线程池执行,避免UI线程阻塞或意外调度:

public class DemoRunClass
{
    public event EventHandler<object> ExecutionUpdate;
    public Dispatcher EventDispatcher;

    public Task ExecuteLongRunningTask(object command, Dispatcher dispatcher)
    {
        EventDispatcher = dispatcher;

        // 改用Task.Run,确保任务在ThreadPool线程执行
        return Task.Run(() =>
        {
            Method1();
            Method2();
            Method3();
            Method4();
            Method5();
            UpdateEvent("All Complete");
        });
    }

    // 其余代码保持不变
}

方案三:传递Item到事件中(更健壮的设计)

彻底消除对共享字段的依赖,将ItemViewModel直接作为事件参数传递,从根源上避免竞态:

  1. 修改DemoRunClass的事件定义:
// 自定义事件参数
public class ExecutionUpdateEventArgs : EventArgs
{
    public object Status { get; }
    public ItemViewModel Item { get; }

    public ExecutionUpdateEventArgs(object status, ItemViewModel item)
    {
        Status = status;
        Item = item;
    }
}

public class DemoRunClass
{
    public event EventHandler<ExecutionUpdateEventArgs> ExecutionUpdate;
    public Dispatcher EventDispatcher;

    // 添加ItemViewModel参数
    public Task ExecuteLongRunningTask(object command, Dispatcher dispatcher, ItemViewModel item)
    {
        EventDispatcher = dispatcher;

        return Task.Run(() =>
        {
            Method1(item);
            Method2(item);
            Method3(item);
            Method4(item);
            Method5(item);
            UpdateEvent("All Complete", item);
        });
    }

    public void UpdateEvent(object status, ItemViewModel item)
    {
        if (!EventDispatcher.CheckAccess())
            EventDispatcher.Invoke(new Action<object, ItemViewModel>(UpdateEvent), status, item);
        else
        {
            ExecutionUpdate?.Invoke(null, new ExecutionUpdateEventArgs(status, item));
        }
    }

    private void Method1(ItemViewModel item)
    {
        // 执行操作
        UpdateEvent("Method1 complete", item);
    }

    // Method2-Method5同理,接收ItemViewModel参数
}
  1. 修改DemoViewModel的调用和事件处理:
public class DemoViewModel
{
    private DemoRunClass DemoRunner;

    public async Task RunNextItem()
    {
        var nextRunnableItemViewModel = GetNextItem();
        var runItemCommand = GenerateCommand(nextRunnableItemViewModel);

        try
        {
            // 传递ItemViewModel给任务方法
            await DemoRunner.ExecuteLongRunningTask(runItemCommand, Dispatcher.CurrentDispatcher, nextRunnableItemViewModel);
        }
        catch (Exception ex)
        {
            // 异常处理
        }
        // 不再需要共享字段和finally块
    }

    private void OnUpdateEvent(object sender, ExecutionUpdateEventArgs e)
    {
        if (e.Status == "Method1 complete")
            e.Item.OnMethod1Complete();
        else if (e.Status == "All Complete")
            e.Item.OnAllComplete();
    }
}

内容的提问来源于stack exchange,提问作者user2790954

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:47:46