在TPL中安全地添加集合

时间:2014-10-22 16:06:50

标签: c# wpf multithreading task-parallel-library

在我的WPF项目中。我有:

public partial class MainWindow : Window
{
    ObservableCollection<Calls> items = new ObservableCollection<Calls>();
public MainWindow()
{
    InitializeComponent();
    icTodoList.ItemsSource = items;
    this.DataContext = new MainViewModel();
}

icTodoListItemsControl,我想为其添加两列。

<DockPanel>
<ItemsControl Height="300" Name="icTodoList" ItemsSource="{Binding Calls}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="60"/>
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <TextBlock Text="{Binding DialingNumber, Mode=OneWay, FallbackValue=' '}" Grid.Column="0"/>
                <TextBlock Text="{Binding DialResult, Mode=OneWay, FallbackValue=' '}" Grid.Column="1"/>
            </Grid>
        </DataTemplate>

    </ItemsControl.ItemTemplate>
</ItemsControl></DockPanel>

对于Calls类,我们有

public class Calls : NotifyUIBase
{
    private string dialingNumber;
    public string DialingNumber
    {
        get { return dialingNumber; }
        set
        {
            dialingNumber = value;
            RaisePropertyChanged();
        }
    }
    public string dialResult;
    public string DialResult {
        get { return dialResult; }
        set
        {
            dialResult = value;
            RaisePropertyChanged();
        }
    }
}

NotifyUIBase继承自INotifyPropertyChanged,它包含RaisePropertyChanged属性,忽略此处写入以节省空间。

现在,点击Start按钮即可获得生产者 - 消费者流程。

    private async void Start_Click(object sender, RoutedEventArgs e)
    {
        var producer = Producer();
        var consumer = Consumer();
        await Task.WhenAll(producer, consumer);
    }

consumer方法中,我们更新ItemsControl。

async Task Consumer()
{
    try
    {
        var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = 50
        };
        var consumerBlock = new ActionBlock<AppointmentReminder>(
remainder =>
{
   Calls c = new Calls();
   c.DialingNumber = "number..";
   c.DialResult = "result...;
   items.Add(c);
},
executionDataflowBlockOptions);
 bufferBlock.LinkTo(
 consumerBlock, new DataflowLinkOptions { PropagateCompletion = true });
                await Task.Delay(500);
          }

然而我有一个例外:

  

这种类型的CollectionView不支持从与Dispatcher线程不同的线程更改其SourceCollection。

发生在这一行:

  

items.Add(C);

我想这是线程问题,所以如何解决?

3 个答案:

答案 0 :(得分:4)

您已经将一些选项传递给ActionBlock;您可以在那里轻松指定UI调度程序:

var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions
{
  TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
};

请注意,它不再并行运行。如果您的操作代码很简单(创建单个对象并设置几个属性),这应该足够了。但是,如果对象创建是CPU密集型的,您可能希望保持并行性:

var transformOptions = new ExecutionDataflowBlockOptions
{
  MaxDegreeOfParallelism = 50
};
var actionOptions = new ExecutionDataflowBlockOptions
{
  TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(),
};
var transformBlock = new TransformBlock<AppointmentReminder, Calls>(reminder =>
{
  Calls c = new Calls();
  c.DialingNumber = "number..";
  c.DialResult = "result...;
  return c;
}, transformOptions);
var consumerBlock = new ActionBlock<Calls>(c =>
{
  items.Add(c);
}, actionOptions);
var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
bufferBlock.LinkTo(transformBlock, linkOptions);
transformBlock.LinkTo(consumerBlock, linkOptions);

答案 1 :(得分:1)

只需获取您的项目。在UI线程上执行添加调用:

remainder =>
{
   Calls c = new Calls();
   c.DialingNumber = "number..";
   c.DialResult = "result...;
   Dispatcher.Invoke(()=> // Get the dispatcher from your window and use it here
   {
       items.Add(c);
   }
},

答案 2 :(得分:0)

在CP上查看Asynchronous Multi-threaded ObservableCollection结束。

我在我的所有项目中使用它,并且从多个TPL线程中添加项目或项目范围从未出现任何问题......