Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/339.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在完成多个任务后继续执行而不阻塞UI线程?_C#_Wpf_Mvvm_Task Parallel Library_Busyindicator - Fatal编程技术网

C# 如何在完成多个任务后继续执行而不阻塞UI线程?

C# 如何在完成多个任务后继续执行而不阻塞UI线程?,c#,wpf,mvvm,task-parallel-library,busyindicator,C#,Wpf,Mvvm,Task Parallel Library,Busyindicator,在我的MVVM应用程序中,我的视图模型调用3种不同的服务方法,将每个方法中的数据转换为通用格式,然后使用属性通知/可观察集合等更新UI 服务层中的每个方法都启动一个新的任务,并将任务返回到视图模型。下面是我的一个服务方法的示例 public class ResourceService { internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<

在我的MVVM应用程序中,我的视图模型调用3种不同的服务方法,将每个方法中的数据转换为通用格式,然后使用属性通知/可观察集合等更新UI

服务层中的每个方法都启动一个新的
任务
,并将
任务
返回到视图模型。下面是我的一个服务方法的示例

public class ResourceService
{
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
    var t = Task.Factory.StartNew(() =>
    {
        //... get resources from somewhere
        return resources;
    });

    t.ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            errorCallback(task.Exception);
            return;
        }
        completedCallback(task.Result);
    }, TaskScheduler.FromCurrentSynchronizationContext());

    return t;
}
}
公共类资源服务
{
内部静态任务LoadResources(Action completedCallback、Action errorCallback)
{
var t=Task.Factory.StartNew(()=>
{
//…从某处获取资源
归还资源;
});
t、 继续(任务=>
{
if(task.IsFaulted)
{
errorCallback(task.Exception);
返回;
}
completedCallback(task.Result);
},TaskScheduler.FromCurrentSynchronizationContext());
返回t;
}
}
下面是调用代码和视图模型的其他相关部分

private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>();

public ICollectionView DataView
{
    get { return _dataView; }
    set
    {
        if (_dataView != value)
        {
            _dataView = value;
            RaisePropertyChange(() => DataView);
        }
    }
}

private void LoadData()
{
    SetBusy("Loading...");

    Data.Clear();

    Task[] tasks = new Task[3]
    {
        LoadTools(),
        LoadResources(),
        LoadPersonel()
    };

    Task.WaitAll(tasks);

    DataView = CollectionViewSource.GetDefaultView(Data);
    DataView.Filter = FilterTimelineData;

    IsBusy = false;
}

private Task LoadResources()
{
    return ResourceService.LoadResources(resources =>
    {
        foreach(var r in resources)
        {
            var d = convertResource(r);
            Data.Add(d);
        }
    },
    error => 
    {
        // do some error handling
    });
}
私有ObservableCollection数据=新ObservableCollection();
公共ICollectionView数据视图
{
获取{return\u dataView;}
设置
{
如果(_dataView!=值)
{
_数据视图=值;
RaisePropertyChange(()=>DataView);
}
}
}
私有void LoadData()
{
SetBusy(“加载…”);
Data.Clear();
任务[]任务=新任务[3]
{
LoadTools(),
LoadResources(),
装卸工
};
Task.WaitAll(任务);
DataView=CollectionViewSource.GetDefaultView(数据);
DataView.Filter=FilterTimelineData;
IsBusy=false;
}
私有任务LoadResources()
{
返回ResourceService.LoadResources(资源=>
{
foreach(资源中的var r)
{
var d=资源(r);
数据.添加(d);
}
},
错误=>
{
//做一些错误处理
});
}
这几乎是可行的,但有几个小问题

第一:在一开始调用
SetBusy
时,在我开始任何任务之前和调用
WaitAll
之前,我将
IsBusy
属性设置为true。这将更新UI并显示BusyIndicator控件,但它不起作用。我还尝试添加简单的字符串属性并绑定它们,但它们也没有被更新。IsBusy功能是基类的一部分,在其他视图模型中也可以使用,在这些视图模型中,我没有运行多个任务,因此我不认为XAML中的属性通知或数据绑定存在问题

所有的数据绑定似乎都是在整个方法完成后更新的。我没有在输出窗口中看到任何“首次异常”或绑定错误,这让我相信UI线程在调用WaitAll之前不知何故被阻塞了

第二:我似乎从服务方法返回了错误的任务。我希望在视图模型转换了回调中所有服务方法的所有结果之后运行
WaitAll
之后的所有操作。但是,如果我从服务方法返回continuation任务,将永远不会调用continuation,并且
WaitAll
将永远等待。奇怪的是,绑定到ICollectionView的UI控件实际上正确地显示了所有内容,我假设这是因为数据是一个可观察的集合,并且CollectionViewSource知道集合已更改的事件。

您可以使用它构建一个在输入任务全部完成时运行的延续

Task[] tasks = new Task[3]
{
    LoadTools(),
    LoadResources(),
    LoadPersonel()
};

Task.Factory.ContinueWhenAll(tasks, t =>
{
    DataView = CollectionViewSource.GetDefaultView(Data);
    DataView.Filter = FilterTimelineData;

    IsBusy = false;
}, CancellationToken.None, TaskContinuationOptions.None, 
   TaskScheduler.FromCurrentSynchronizationContext());
请注意,如果使用C#5的
await
/
async
语法,这将变得更简单:

private async void LoadData()
{
    SetBusy("Loading...");

    Data.Clear();

    Task[] tasks = new Task[3]
    {
        LoadTools(),
        LoadResources(),
        LoadPersonel()
    };

    await Task.WhenAll(tasks);

    DataView = CollectionViewSource.GetDefaultView(Data);
    DataView.Filter = FilterTimelineData;

    IsBusy = false;
}

但是,如果我从服务方法返回continuation任务,那么continuation将永远不会被调用,WaitAll将永远等待

问题是您的延续任务需要UI线程,并且您在
WaitAll
调用中阻塞了UI线程。这会造成无法解决的死锁

修复上面的问题应该可以纠正这一点-您希望将延续作为任务返回,因为这是您需要等待完成的任务-但是通过使用
TaskFactory.continuewhalll
可以释放UI线程,以便它可以处理这些延续

注意,这是另一件用C#5简化的事情。您可以将其他方法编写为:

internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback)
{
  try
  {
    await Task.Run(() =>
    {
        //... get resources from somewhere
        return resources;
    });
  }
  catch (Exception e)
  {
    errorCallback(task.Exception);
  }

  completedCallback(task.Result);
}
内部静态异步任务LoadResources(Action completedCallback,Action errorCallback)
{
尝试
{
等待任务。运行(()=>
{
//…从某处获取资源
归还资源;
});
}
捕获(例外e)
{
errorCallback(task.Exception);
}
completedCallback(task.Result);
}

也就是说,编写返回
任务的方法通常比提供回调要好,因为这样可以简化使用的两端。

我一直推迟使用async/await,因为我不确定它是否“只适用于”WPF命令和Prism DelegateCommand。Task.ContinueWhenAll对我来说似乎不存在,我需要引用一些TPL扩展库吗?@BenCr抱歉-它是TaskFactory.ContinueWhenAll。一般来说,wait/async工具比WPF中的任务延续更有效。主要的区别是,你不必为了处理异常而跳出疯狂的圈套。感谢里德,这似乎工作得更好。希望有人能够在调用WaitAll之前对阻塞进行解释,但这肯定解决了问题1和2。我将在下一次迭代中尝试async/await的内容,看看我的进展如何。@BenCR我刚刚编辑了我的文章来解释它。基本上,WaitAll会在任务完成之前阻塞UI,但任务需要UI来完成;)@BenCr:Async/await比使用具有连续性的任务(以及来自同步上下文的任务调度器等)容易得多。如果需要
async
命令,可以使用。