visualstudio绘画装饰与懒惰的更新

本文关键字:更新 绘画 visualstudio | 更新日期: 2023-09-27 18:21:55

我正在制作一个Visual Studio装饰扩展。如果至少2秒没有用户输入,我想更新装饰。所以我构建了一个worker,并试图删除和添加装饰,但VS说它不能更新,因为非ui线程调用了它。所以我在没有线程的情况下等待,然后我的编辑器变得非常滞后(因为ui线程在等待)

我想知道是否有一种方法可以通过延迟更新来更新装饰。绘制装饰是通过调用AddAdlement()来完成的,我找不到如何调用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;
    }

visualstudio绘画装饰与懒惰的更新

我不知道你为什么被否决,特别是因为在处理扩展时这是一个有趣的问题。

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

首先,复习一下如何在VisualStudio扩展中处理从后台线程到UI线程的切换。我发现使用JoinableTaskFactory在VS中进行异步和多线程编程有助于解释。

我不得不用一个昂贵的解析操作做一些类似的事情。这是非常直接的。

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

  1. 它使用async void事件处理程序订阅ITextBuffer.ChangedLowPriority事件
  2. 一旦启动,它就会通过CancellationToken.Cancel()调用取消正在进行的任何解析操作。取消令牌被传递到所有支持它的东西中(在Roslyn中,它在任何你想要的地方都得到支持)
  3. 它开始解析操作,但在开始之前,我有一个Task.Delay(200, m_cancellationToken)调用。基于我的打字速度和Roslyn的操作对任何昂贵的东西都有CancellationToken重载的事实,我的200毫秒(我的解析工作也很轻)。YMMV

我使用的WPF组件非常需要UI线程,并且它们混合在IViewModelTaggerIWpfTextViewListener中。它们足够轻,我本可以跳过异步处理,但在非常大的类上,它们可以挂起UI。

为了处理这个问题,我做了以下操作:

  1. 在TextViewLayoutChanged上,我订阅了一个async void事件处理程序
  2. 我先Task.Run()昂贵的操作,防止UI被阻塞
  3. 当我最终创建WPF UI元素并将其作为装饰完成添加时(以及SDK中需要它的几个操作),我await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()来获得UI线程

我提到了"其他SDK操作",这很重要。在SDK中,除了主线程之外,还有几件事你不能在任何东西上做(内存现在让我很失望,但如果在后台线程上访问TextView的某些部分,它们尤其会失败,而且不会始终如一)。

有更多的选项用于执行UI线程的工作(普通Task.Run工作,以及ThreadHelper.JoinableTaskFactory.Run工作)。在我回答的早些时候,链接到Andrew Arnott的帖子解释了所有的选择。你会想完全理解这一点,因为根据任务的不同,有理由使用一些而不是其他。

希望能有所帮助!

代码中使用的

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

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

这实际上意味着"启动一个等待2秒的计时器,然后一旦它完成,就在UI线程上运行DoWork方法。如果发生了更多的键入,那么你可以调用cancellTokenSource.Cancel(),然后再次运行。

此外,我必须询问您的类型"CSharpRegionParser"。如果您需要区域信息,并且使用的是Visual Studio 2015,那么您可以从Roslyn获得语法树,并且您应该关注工作区更改事件,而不是挂钩LayoutChanged。你最好把你的系统构建成一对标签工/装饰经理,因为写起来可能更清楚。。。我不清楚为什么要在LayoutChanged中进行解析逻辑,因为LayoutChanged是在视觉布局过程中发生的事情,包括滚动、调整大小等。