视觉工作室绘图装饰与懒惰更新

时间:2015-11-15 07:23:32

标签: c# visual-studio visual-studio-extensions

我正在制作Visual Studio装饰扩展程序。如果没有用户输入至少2秒,我想更新装饰品。所以我构建了一个工人,并尝试删除和添加装饰,但VS说它无法更新,因为非ui线程已经调用它。所以我没有线程等待,然后我的编辑器变得非常迟缓(因为ui线程等待)

我想知道是否有办法使用延迟更新来更新装饰品。 绘图装饰是通过调用AddAdornment()完成的,我找不到如何调用ui线程来绘制。

以下是我的代码

    internal async void OnLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
    {
        Print("OnLayoutChanged Called");

        task = Task.Factory.StartNew(() =>
        {
            Print("task Started");
            if (e.NewSnapshot != e.OldSnapshot)
            {
                parseStopwatch.Restart();
                shouldParse = true;
            }

            ParseWork(e);
        });
        await task;


    }


    private async void ParseWork(object param)
    {
        var e = (TextViewLayoutChangedEventArgs)param;
        if (e == null)
        {
            shouldParse = false;
            parseStopwatch.Stop();
            CsharpRegionParser.ParseCs(this.view.TextSnapshot);
            DrawRegionBox();
            return;
        }

        while (shouldParse)
        {
            Task.Delay(10);
            if ((shouldParse && parseStopwatch.ElapsedMilliseconds > 2000) || parseStopwatch.ElapsedMilliseconds > 5000)
            {
                break;
            }

        }
        shouldParse = false;
        parseStopwatch.Stop();
        CsharpRegionParser.ParseCs(this.view.TextSnapshot);
        DrawRequest(e);

        return;

    }

2 个答案:

答案 0 :(得分:4)

我不确定你为什么会被投票,特别是因为在处理扩展时这是一个有趣的问题。

因此,对于您的第一个问题:Visual Studio与WPF具有相同的要求(由于其COM依赖性而增加了一些复杂性)。当您不在主(UI)线程上时,您无法更新UI元素。不幸的是,如果你直接潜入并使用你用于WPF的策略来接近它,你将会经历另一个问题世界(主要是死锁)。

首先,了解如何在Visual Studio扩展域中处理从后台切换到UI线程的问题。我发现Asynchronous and Multithreaded programming within VS using JoinableTaskFactory有助于解释。

我必须通过昂贵的解析操作做类似的事情。这很直接。

我的解析器作为IViewModelTagger实例的一部分执行,并使用以下序列(粗略地):

  1. 它使用ITextBuffer.ChangedLowPriority事件处理程序订阅async void事件。
  2. 立即着火,它通过CancellationToken.Cancel()电话取消正在进行的任何解析操作。取消令牌将传递到支持它的所有内容中(在Roslyn中,它可以在任何您希望的地方得到支持)。
  3. 它开始解析操作,但在启动之前,我有一个Task.Delay(200, m_cancellationToken)调用。根据我的打字速度和Roslyn的操作CancellationToken重载任何昂贵的东西(我的解析工作也非常轻量级),我200ms。 YMMV。
  4. 我使用需要UI线程的WPF组件,并且它们在IViewModelTaggerIWpfTextViewListener内混合在一起。它们的重量足够轻,我可以跳过它们的异步,但是在非常大的类中它们可以挂起UI。

    为了解决这个问题,我做了以下几点:

    1. 在TextViewLayoutChanged上,我订阅了一个async void事件处理程序。
    2. Task.Run()首先进行昂贵的操作,防止UI被阻止。
    3. 当我最终创建WPF UI元素并将其添加为装饰完成时(以及需要它的SDK中的一些操作),我await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()来获取UI线程。
    4. 我提到过"其他SDK操作",这很重要。 SDK中除了主线程之外的任何事情都有一些你不能做的事情(内存现在让我失望,但特别是TextView的某些部分会失败,如果它们在后台线程上被访问则不一致)。 / p>

      有更多选项可以执行UI线程的工作(普通Task.Run工作,以及ThreadHelper.JoinableTaskFactory.Run)。在我的回答中早先链接的Andrew Arnott帖子解释了所有的选择。您将完全理解这一点,因为根据任务的不同,有理由使用其他语言。

      希望有所帮助!

答案 1 :(得分:0)

代码中使用的

Task.Delay返回一个在您延迟时完成的任务。如果你这样称它并忽略结果,它就没有做你想象的那样。你可能想要的是,而不是像你一样调用Task.Factory.StartNew,你想要:

var cancellationTokenSource = new CancellationTokenSource();
Task.Delay(2000, cancellationTokenSource.Token).ContinueWith(() => DoWork(), cancellationTokenSource.Token, TaskScheduler.Current).

这说明有效地启动了一个等待2秒的计时器,然后一旦完成就在UI线程上运行DoWork方法。如果发生更多的输入,那么你可以调用cancellationTokenSource.Cancel()并再次运行。

另外,我不得不问你的类型" CSharpRegionParser"。如果您需要区域信息并且您在Visual Studio 2015上,那么您可以从Roslyn获取语法树,您应该观察工作区更改事件而不是挂钩LayoutChanged。您最好将系统构建为标记器/装饰管理器对,因为编写它可能更清楚......我不清楚为什么要在LayoutChanged中解析逻辑LayoutChanged是在视觉布局期间发生的事情,包括滚动,调整大小等。