c# - 在 TPL 中安全添加集合

标签 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();
}

icTodoList 是一个 ItemsControl,我想为其添加两列。

<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属性,此处忽略写入以节省空间。

现在,我通过单击“开始”按钮获得了一个生产者-消费者流程。

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

但是我有一个异常(exception):

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

事情发生在线路上:

items.Add(c);

我猜是线程问题,那么如何解决呢?

最佳答案

您已经将一些选项传递给您的 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);

关于c# - 在 TPL 中安全添加集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26511997/

相关文章:

c# - Nullable (int?) 不断抛出不可为空的异常

c# - Azure Function v2 中的 BrokeredMessage 发送和消息使用者

c# - Entity Framework - 使用简单查询时速度慢

wpf - 带有动态文本和文本颜色更新的进度条

c# - 使用 CSWinRT 调用 .net5 windows api

c# - 基于其他列合并 C# 列表中一列的多行

wpf - 在WPF中,Visual.PointFromScreen在什么情况下会引发InvalidOperationException?

java - 使用 Java 客户端在 Cassandra 中进行多线程异步写入

java - 立即停止线程的最佳实践

c# - 我在线程方面做错了什么?