如何:从后台线程中更新 UML 模型

有时,在后台线程中对模型进行更改会很有用。 例如,如果正在从速度较慢的外部资源加载信息,则可以使用后台线程来管理更新内容。 这使用户可以在每次出现更新时看到相应的更新。

但您必须了解 UML 存储区不是线程安全的。 下面的预防措施非常重要:

  • 对模型或关系图的每次更新都必须在用户界面 (UI) 线程中进行。 后台线程必须使用 ControlInvoke() 或 DispatcherInvoke() 才能使 UI 线程执行实际更新。

  • 如果将一系列更改组合到单个事务中,我们建议您阻止用户在事务执行过程中编辑模型。 否则,用户所做的任何编辑都将成为同一事务的一部分。 可以通过显示模式对话框来阻止用户进行更改。 如果需要,可以在该对话框中提供一个“取消”按钮。 用户可以在发生更改时看到相应更改。

示例

此示例使用后台线程对模型进行几项更改, 并使用了一个对话框来在运行该线程时排除用户。 在此简单示例中,该对话框中未提供“取消”按钮。 不过,添加此功能很容易。

运行示例

  1. 在 C# 项目中创建一个命令处理程序,如如何:在建模图上定义菜单命令中所述。

  2. 确保该项目包含对这些程序集的引用:

    • Microsoft.VisualStudio.ArchitectureTools.Extensibility

    • Microsoft.VisualStudio.Modeling.Sdk.10.0

    • Microsoft.VisualStudio.Modeling.Sdk.Diagrams.10.0

    • Microsoft.VisualStudio.Uml.Interfaces

    • System.ComponentModel.Composition

    • System.Windows.Forms

  3. 向该项目中添加一个名为 ProgressForm 的 Windows 窗体。 此窗体应显示一条指示正在进行更新的消息, 但不必包含任何其他控件。

  4. 添加一个 C# 文件,其中包含执行步骤 7 后显示的代码。

  5. 生成并运行该项目。

    一个新的 Visual Studio 实例将以实验模式启动。

  6. 在 Visual Studio 实验实例中创建或打开一个 UML 类关系图。

  7. 在此 UML 类图中的任意位置右击,然后单击**“添加若干 UML 类”**。

关系图中将以 0.5 秒的时间间隔接连显示若干新的类框。

using System;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Threading;
using System.Windows.Forms;

using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling.ExtensionEnablement;
using Microsoft.VisualStudio.Uml.Classes;

namespace BackgroundThreadProgressUI
{
  [Export(typeof(ICommandExtension))]
  [ClassDesignerExtension]
  class UmlClassAdderCommand : ICommandExtension
  {

    [Import]
    IDiagramContext context { get; set; }

    [Import]
    IServiceProvider serviceProvider { get; set; }

    [Import]
    ILinkedUndoContext linkedUndoContext { get; set; }

    // Called when the user runs the command.
    public void Execute(IMenuCommand command)
    {
      // The form that will exclude the user.
      ProgressForm form = new ProgressForm();

      // System.ComponentModel.BackgroundWorker is a
      // convenient way to run a background thread.
      BackgroundWorker worker = new BackgroundWorker();
      worker.WorkerSupportsCancellation = true;

      worker.DoWork += delegate(object sender, DoWorkEventArgs args)
      {
        // This block will be executed in a background thread.

        IClassDiagram diagram = context.CurrentDiagram as IClassDiagram;
        IModelStore store = diagram.ModelStore;
        const int CLASSES_TO_CREATE = 15;

        // Group all the changes together.
        using (ILinkedUndoTransaction transaction = linkedUndoContext.BeginTransaction("Background Updates"))
        {
          for (int i = 1; i < CLASSES_TO_CREATE; i++)
          {
            if (worker.CancellationPending) 
               return; // No commit - undo all.

            // Create model elements using the UI thread by using
            // the Invoke method on the progress form. Always 
            // modify the model and diagrams from a UI thread.
            form.Invoke(new MethodInvoker(delegate()
            {
              IClass newClass = store.Root.CreateClass();
              newClass.Name = string.Format("NewClass{0}", i);
              diagram.Display(newClass);
            }));
            

            // Sleep briefly so that we can watch the updates.
            Thread.Sleep(500);
          }
          
          // Commit the transaction or it will be rolled back.
          transaction.Commit();
        }
      };

      // Close the form when the thread completes.
      worker.RunWorkerCompleted += delegate(object sender, RunWorkerCompletedEventArgs args)
      {
        form.Close();
      };

      // Start the thread before showing the modal progress dialog.
      worker.RunWorkerAsync();

      // Show the form modally, parented on VS.
      // Prevents the user from making changes while in progress.
      form.ShowDialog();
    }

    public void QueryStatus(IMenuCommand command)
    {
    }

    public string Text
    {
      get { return "Add several classes"; }
    }
  }
}

允许用户取消示例中的线程

  1. 向进程对话框添加一个“取消”按钮。

  2. 向进程对话框添加下列代码:

    public event MethodInvoker Cancel;

    private void CancelButton_Click(object sender, EventArgs e)

    {

    Cancel();

    }

  3. 在 Execute() 方法中,在窗体构造后插入此行:

    form.Cancel += delegate() { worker.CancelAsync(); };

访问 UI 线程的其他方法

如果不需要创建对话框,则可以访问显示关系图的控件:

DiagramView uiThreadHolder = context.CurrentDiagram.GetObject<Diagram>().ActiveDiagramView;

可以使用 uiThreadHolder.Invoke() 在 UI 线程中执行操作。

请参见

概念

如何:在建模图上定义菜单命令

其他资源

如何:在建模图上定义放置和双击处理程序