如何将多个任务后,继续不阻塞UI线程? [英] How do I continue after multiple Tasks without blocking the UI thread?

查看:241
本文介绍了如何将多个任务后,继续不阻塞UI线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的MVVM应用我的看法模式要求3种不同的服务方法,从每个数据转换成统一格式,然后用物业的通知/观察到的藏品等。



更新UI

在服务层的每个方法启动一个新的工作并返回工作来视图模型。下面是我的服务方法的一个例子。

 公共类ResourceService 
{
内部静态任务LoadResources (动作< IEnumerable的<资源>> completedCallback,动作<异常> errorCallback)
{
变种T = Task.Factory.StartNew(()=>
{
/ / ...获取资源从某处
返回资源;
});

t.ContinueWith(任务=>
{
如果(task.IsFaulted)
{
errorCallback(task.Exception);
返回;
}
completedCallback(task.Result);
},TaskScheduler.FromCurrentSynchronizationContext());

返回吨;
}
}

下面是该视图的调用代码和其他相关部分模型...

 私人的ObservableCollection<&DataItem的GT;数据=新的ObservableCollection<&DataItem的GT;(); 

公众的ICollectionView数据视图
{
{返回_dataView; }

{
如果(_dataView =价值!)
{
_dataView =价值;
RaisePropertyChange(()=>数据视图);
}
}
}

私人无效LoadData()
{
SetBusy(正在加载...);

Data.Clear();

工作[]任务=新任务[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};

Task.WaitAll(任务);

数据视图= CollectionViewSource.GetDefaultView(数据);
DataView.Filter = FilterTimelineData;

IsBusy = FALSE;
}

私人任务LoadResources()
{
返回ResourceService.LoadResources(资源=>
{
的foreach(VAR R IN资源)
{
变种D = convertResource(R);
Data.Add(D);
}
},
错误=>
{
//做一些错误处理
});
}

这几乎工作,但也有几个小问题。



1号:在调用 SetBusy 在开始的时候,我才开始任何任务,我打电话之前为WaitAll ,我在 IsBusy 属性设置为true。这应该更新用户界面,并显示BusyIndi​​cator控件,但它不工作。我也尝试添加简单的字符串属性和具有约束力的,他们还没有被任何更新。该IsBusy功能是一个基类的一部分,在其他视图模型作品,我没有更多的多个任务运行,所以我不相信这是与物业通知或数据在XAML绑定的问题。



所有数据绑定似乎整个方法完成后,必须更新。我没有看到任何第一次例外,或绑定错误在输出窗口这是导致我相信UI线程以某种方式被调用为WaitAll之前受阻。



2号:我似乎回到从服务方法错了任务。我想以后为WaitAll 一切运行视图模型转换从回调的所有服务方法的所有结果之后。但是,如果我从服务方法返回的后续任务的延续不会被调用和为WaitAll 将永远等待。奇怪的是绑定到该ICollectionView中UI控件实际上正确显示的一切,我以为这是因为数据是观察的集合和CollectionViewSource知道集合变化事件。


解决方案

您可以使用的 TaskFactory.ContinueWhenAll 以创建运行的延续,当输入任务的所有完整。

 任务[] =任务新的Task [3] 
{
LoadTools(),
LoadResources(),
LoadPersonel()
};

Task.Factory.ContinueWhenAll(任务,T =>
{
数据视图= CollectionViewSource.GetDefaultView(数据);
DataView.Filter = FilterTimelineData;

IsBusy = FALSE;
},CancellationToken.None,TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());

请注意,这变得更简单,如果你使用C#5的的await / 异步语法:

 专用异步无效LoadData() 
{
SetBusy(正在加载...);

Data.Clear();

工作[]任务=新任务[3]
{
LoadTools(),
LoadResources(),
LoadPersonel()
};

等待Task.WhenAll(任务);

数据视图= CollectionViewSource.GetDefaultView(数据);
DataView.Filter = FilterTimelineData;

IsBusy = FALSE;
}







但是,如果我从服务方法返回的后续任务的延续不会被调用,并为WaitAll永远等待




的问题是,你的延续任务需要UI线程,和您阻止在为WaitAll 调用UI线程。这就产生,不会解决死锁



修复上面应该纠正这一点 - 你想要回续书的任务,因为这是你需要等待什么完成 - 但通过使用 TaskFactory.ContinueWhenAll 您腾出UI线程,因此它可以处理这些延续



请注意,这是一个被用C#5,你可以写你的其他方法简化了另一件事:

 内部静态异步任务LoadResources (动作< IEnumerable的<资源>> completedCallback,动作<异常> errorCallback)
{

{
等待Task.Run(()=>
{
// ...获取资源从某处
返回资源;
});
}
赶上(例外五)
{
errorCallback(task.Exception);
}

completedCallback(task.Result);
}



话虽这么说,这是典型的最好编写方法返回一个任务< T> ,而不是提供回调,因为这简化了使用的两端


In my MVVM application my view model calls 3 different service methods, converts the data from each into a common format and then updates the UI using property notification/observable collections etc.

Each method in the service layer starts a new Task and returns the Task to the view model. Here's an example of one of my service methods.

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;
}
}

Here's the calling code and other relevant parts of the view model...

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
    });
}

This almost works but there are a couple of small issues.

Number 1: In the call to SetBusy at the very beginning, before I start any tasks and before I call WaitAll, I set the IsBusy property to true. This should update the UI and show the BusyIndicator control but it's not working. I've also tried adding simple string properties and binding those and they're not being updated either. The IsBusy functionality is part of a base class and works in other view models where I don't have more than one Task running so I don't believe there is an issue with the property notification or data binding in the XAML.

All the data bindings seem to be updated after the whole method has completed. I'm not seeing any "first time exceptions" or binding errors in the output Window which is leading me to believe the UI thread is somehow being blocked before the call to WaitAll.

Number 2: I seem to be returning the wrong Tasks from the service methods. I want everything after WaitAll to run after the view model has converted all the results from all the service methods in the callbacks. However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever. The strange thing is the UI control bound to the ICollectionView actually displays everything correctly, I assumed this is because Data is an observable collection and the CollectionViewSource is aware of the collection changed events.

解决方案

You can use TaskFactory.ContinueWhenAll to build a continuation that runs when the input tasks all complete.

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());

Note that this becomes simpler if you use C# 5's await/async syntax:

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;
}


However if I return the continuation task from the service method the continuation never gets called and WaitAll waits forever

The problem is that your continuation task requires the UI thread, and you're blocking the UI thread in the WaitAll call. This creates a deadlock which will not resolve.

Fixing the above should correct this - you'll want to return the Continuation as the Task, as that's what you need to wait for completion - but by using TaskFactory.ContinueWhenAll you free up the UI thread so it can process those continuations.

Note that this is another thing that gets simplified with C# 5. You can write your other methods as:

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);
}

That being said, it's typically better to write the methods to return a Task<T> instead of providing callbacks, as that simplifies both ends of the usage.

这篇关于如何将多个任务后,继续不阻塞UI线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆