如何:从后台线程中更新 UML 模型
有时,在后台线程中对模型进行更改会很有用。 例如,如果正在从速度较慢的外部资源加载信息,则可以使用后台线程来管理更新内容。 这使用户可以在每次出现更新时看到相应的更新。
但您必须了解 UML 存储区不是线程安全的。 下面的预防措施非常重要:
对模型或关系图的每次更新都必须在用户界面 (UI) 线程中进行。 后台线程必须使用 ControlInvoke() 或 DispatcherInvoke() 才能使 UI 线程执行实际更新。
如果将一系列更改组合到单个事务中,我们建议您阻止用户在事务执行过程中编辑模型。 否则,用户所做的任何编辑都将成为同一事务的一部分。 可以通过显示模式对话框来阻止用户进行更改。 如果需要,可以在该对话框中提供一个“取消”按钮。 用户可以在发生更改时看到相应更改。
示例
此示例使用后台线程对模型进行几项更改, 并使用了一个对话框来在运行该线程时排除用户。 在此简单示例中,该对话框中未提供“取消”按钮。 不过,添加此功能很容易。
运行示例
在 C# 项目中创建一个命令处理程序,如如何:在建模图上定义菜单命令中所述。
确保该项目包含对这些程序集的引用:
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
向该项目中添加一个名为 ProgressForm 的 Windows 窗体。 此窗体应显示一条指示正在进行更新的消息, 但不必包含任何其他控件。
添加一个 C# 文件,其中包含执行步骤 7 后显示的代码。
生成并运行该项目。
一个新的 Visual Studio 实例将以实验模式启动。
在 Visual Studio 实验实例中创建或打开一个 UML 类关系图。
在此 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"; }
}
}
}
允许用户取消示例中的线程
向进程对话框添加一个“取消”按钮。
向进程对话框添加下列代码:
public event MethodInvoker Cancel;
private void CancelButton_Click(object sender, EventArgs e)
{
Cancel();
}
在 Execute() 方法中,在窗体构造后插入此行:
form.Cancel += delegate() { worker.CancelAsync(); };
访问 UI 线程的其他方法
如果不需要创建对话框,则可以访问显示关系图的控件:
DiagramView uiThreadHolder = context.CurrentDiagram.GetObject<Diagram>().ActiveDiagramView;
可以使用 uiThreadHolder.Invoke() 在 UI 线程中执行操作。