【问题标题】:How to visualize a blockingcollection如何可视化blockingcollection
【发布时间】:2012-12-13 13:35:09
【问题描述】:

我觉得我现在的逻辑有些问题。

我使用了一个阻塞集合来对其他 PC 进行线程安全调用。一般来说,它看起来像这样:

public class MyClass
{
  private BlockingCollection<workUnit> workUnits = new BlockingCollection<workUnit>();

  public void EnQueue(workUnit item)
  {
    workUnits.Add(item);
  }

  private void DeQueue()
  {
    while (!stopFlag)
    {
      workUnit item = workUnits.Take();
      DoLongRunningDBStuff(workUnit);
    }
  }
} 

现在我想将其可视化给用户。

用户应该看到

  1. 项目在队列中
  2. 项目处理已开始
  3. 处理结果(主要是通过/失败/异常)

现在我有些头疼。

我正在考虑执行以下操作:

有一个网格来向用户显示项目。

  1. 如果项目已入队,则将其添加到工作单元以及绑定到数据网格的列表中
  2. 如果项目已出列(已使用),请更新网格列表中的项目。

让人头疼的是,如何让这个线程安全,以及哪些部分需要是线程安全的。

如果我将一些需要时间的东西放在 workUnit.Add 后面,我认为这可能是可能的,那么数据就会混合在一起。

这样的事情可行吗?

  1. 如果项目已入队,请将其添加到工作单元和 UI 的附加 BlockingCollection
  2. 如果项目已出列,请尝试 2​​.BlockingCollection 并将其删除,更新状态并再次将其附加到第二个列表。

我需要在 1 和 2 附近加锁吗?如果是这样,如果等待Take,它不会完全阻止添加吗?

是否有简单的解决方案或方法来可视化,发生了什么?

【问题讨论】:

  • 这是一个非常严重的阻抗失配,BlockingCollection 是线程代码中使用的一个类。 UI 基本上是线程不安全的。也存在大幅减慢代码速度的严重风险。最好不要这样做,真的。这是用户永远不必担心的实现细节。
  • @Hans:主要不是展示内部实现细节。
  • {总是忘记不要使用 enter here) 考虑以下用例:数据发送到多台 PC。这通常需要约 150 毫秒,但也可能需要长达 3 秒。在以前的产品中,有一条状态行显示数据已发送。因此,用户习惯并期望后继者也将显示信息,并且他们有一个屏幕,可以看到未成功发送的内容并可以手动触发重新发送。前者用于用户反馈某些事情正在实际发生。后者用于用户交互。
  • 例如用于添加某人可以重复失败的工作或可以查找失败原因的值(机器停机,对方版本错误,双方都更改了数据和他们必须手动确认哪个是正确的,依此类推。)

标签: c# multithreading user-interface


【解决方案1】:

我会尝试这样做:

public class MyClass
{
  private BlockingCollection<workUnit> workUnits = new BlockingCollection<workUnit>();

  public void EnQueue(workUnit item)
  {
    workUnits.Add(item);
  }

  private void DeQueue()
  {
    while (!stopFlag)
    {
      workUnit item = workUnits.Take();
      item.SetState("Processing Started");
      try
      {
          DoLongRunningDBStuff(workUnit);
          item.SetState("Processing Successful");
      }
      catch
      {
          item.SetState("Processing Failed");
      }
    }
  }
} 

在本例中,我将让workItem.SetState(...) 触发一个事件,该事件将更新特定项目的 UI。但是,由于该事件是在非 UI 线程中引发的,因此需要将更新发布到UI 线程(例如,如果您使用 WinForms,您将调用显示数据的控件的 Invoke 方法)。

在另一个(首选)建议中,我会执行以下操作(如果您可以在 .NET 4.0 及更高版本中使用TPL):

public class MyClass
{
  public Task EnQueue(workUnit item)
  {
    // Schedule the work on the thread pool. 
    // If you need limited concurrency here, there are schedulers to enable this.
    return Task.Run(() => DoLongRunningDBStuff(item));
  }
} 

如果您使用 .NET 4.5,您将能够使用 await 功能,该功能将自动同步 UI 线程上下文中的任务继续。例如。在调用方(假设它是在 UI 线程上启动的),您只需执行以下操作:

private async void btnAddItem_Click(object sender, EventArgs e)
{
    var item = new workUnit();

    // TODO: Add item on UI here

    try
    {
        await myClass.EnQueue(item);

        // TODO: Update UI with success result here (no context synchronisation is needed here it is already in the UI context)
    }
    catch
    {
        // TODO: Update UI with error result here (no context synchronisation is needed here it is already in the UI context)
    }
}

在这两个示例中,您甚至不需要任何锁定,您只需将更新发布到正确的上下文(在最后一个甚至没有明确需要的示例中,编译器会为您处理)

【讨论】:

  • 嗨,.net 4.0 或 4.5 都可以,但该作业通常不是由 ui 线程初始化,而是由后台线程初始化。因此我认为“等待”不是解决方案。有 n 个线程可以提供任务,但在连接上,一次应该只有一个线程在运行。因此,我使用了同步方法 dolongrunningstuff 的入队和出队。 (好的,该类有多个实例,但每个客户端只有一个实例)。我的问题主要是如何表示添加了这个东西。不是 workUnits.Add(item);其次是 item.SetState 不是线程安全的?
  • @Offler 任务是由后台线程还是由它自己的 UI 发起的,并不意味着 UI 不能等待创建的任务。此外,任务不必由 n 线程处理。您可以使用在 official TPL examples 找到的调度程序之一,它将在单个线程上执行任务(并按照创建顺序)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
  • 2017-12-11
  • 2019-11-18
  • 1970-01-01
  • 2012-02-24
  • 1970-01-01
相关资源
最近更新 更多