简体   繁体   English

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

[英]visual studio drawing adornment with lazy update

I'm making an Visual Studio adornment extension. 我正在制作Visual Studio装饰扩展。 I want to update adornments if there is no user input at least 2 seconds. 如果没有用户输入至少2秒,我想更新装饰品。 So i constructed a worker and tried to remove and add adornment but VS says it can't be updated because non-ui thread had called it. 所以我构建了一个工人,并尝试删除和添加装饰,但VS说它无法更新,因为非ui线程已经调用它。 So I waited without thread then my editor goes really laggy (because the ui thread waits) 所以我等了没有线程然后我的编辑器变得非常滞后(因为ui线程等待)

I want to know if there is a way to update adornments with lazy update. 我想知道是否有办法使用延迟更新来更新装饰品。 Drawing adornment is done by calling AddAdornment(), and i can't find how to invoke ui thread to draw. 绘制装饰是通过调用AddAdornment()来完成的,我找不到如何调用ui线程来绘制。

Below is my code 以下是我的代码

    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;

    }

I'm not sure why you were down-voted, particularly because this is an interesting problem when dealing with extensions. 我不确定你为什么会被投票,特别是因为在处理扩展时这是一个有趣的问题。

So, to your first problem: Visual Studio has the same requirements as WPF (with some added complications due to its COM dependency). 因此,对于您的第一个问题:Visual Studio与WPF具有相同的要求(由于其COM依赖性而增加了一些复杂性)。 You can't update a UI element when you're not on the Main (UI) thread. 当您不在主(UI)线程上时,无法更新UI元素。 Unfortunately, if you dive right in and approach it using the strategies you'd use for WPF, you'll experience a whole other world of problems (deadlocks, mostly). 不幸的是,如果你直接潜入并使用你用于WPF的策略来接近它,你将会经历另一个问题世界(主要是死锁)。

First things first, brush up on how to handle switching from background to UI threads in Visual Studio extension land. 首先,了解如何在Visual Studio扩展域中处理从后台切换到UI线程的问题。 I found Asynchronous and Multithreaded programming within VS using JoinableTaskFactory to be helpful in explaining. 在VS中使用JoinableTaskFactory找到了异步和多线程编程 ,以帮助解释。

I had to do something similar with an expensive parsing operation. 我不得不用昂贵的解析操作做类似的事情。 It was pretty straight forward. 这很直接。

My parser executes as part of an IViewModelTagger instance and uses the following sequence (roughly): 我的解析器作为IViewModelTagger实例的一部分执行,并使用以下序列(粗略地):

  1. It subscribes to the ITextBuffer.ChangedLowPriority event with an async void event handler. 它使用async void事件处理程序订阅ITextBuffer.ChangedLowPriority事件。
  2. Immediately on fire, it cancels any parsing operation in progress via a CancellationToken.Cancel() call. 立即着火,它通过CancellationToken.Cancel()调用CancellationToken.Cancel()正在进行的任何解析操作。 The cancellation token is passed into everything that supports it (in Roslyn, it's supported everywhere you would want it to be). 取消令牌将传递到支持它的所有内容中(在Roslyn中,它支持您希望它的所有位置)。
  3. It begins the parsing operation, but before starting it, I have a Task.Delay(200, m_cancellationToken) call. 它开始解析操作,但在启动之前,我有一个Task.Delay(200, m_cancellationToken)调用。 I 200ms based on my typing speed and the fact that Roslyn's operations have CancellationToken overloads for anything expensive (my parsing work is pretty light-weight, too). 我200ms是基于我的打字速度和Roslyn的操作有任何昂贵的CancellationToken重载的事实(我的解析工作也非常轻量级)。 YMMV. 因人而异。

I work with WPF components that require the UI thread quite a bit and they're intermingled within the IViewModelTagger and the IWpfTextViewListener . 我使用需要UI线程的WPF组件,它们在IViewModelTaggerIWpfTextViewListener混合IWpfTextViewListener They're lightweight enough that I could have skipped async'ing them, but on very large classes they can hang the UI. 它们的重量足够轻,我可以跳过异步它们,但是在非常大的类中它们可以挂起UI。

To handle this, I did the following: 为了解决这个问题,我做了以下工作:

  1. On the TextViewLayoutChanged, I subscribe with an async void event handler. 在TextViewLayoutChanged上,我订阅了一个async void事件处理程序。
  2. I Task.Run() the expensive operations first, preventing the UI from being blocked. Task.Run()执行昂贵的操作Task.Run() ,防止UI被阻止。
  3. When I do the final creation of the WPF UI elements and add them as adornments finalization (along with a couple of operations within the SDK that require it), I await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync() to get the UI thread. 当我最终创建WPF UI元素并将它们添加为装饰完成时(以及需要它的SDK中的一些操作),我await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()来获取UI线程。

I mentioned "other SDK operations", that's important. 我提到了“其他SDK操作”,这很重要。 There are several things you cannot do within the SDK on anything but the Main thread (memory is failing me now, but parts of the TextView in particular will fail, and not consistently, if they're accessed on background threads). SDK中除了主线程之外的任何事情都有一些你不能做的事情(内存现在让我失望,但特别是TextView的部分会失败,如果在后台线程上访问它们则不一致)。

There are more options for executing the work off of the UI thread (Ordinary Task.Run works, as well as ThreadHelper.JoinableTaskFactory.Run ). 有更多的选项来执行UI线程的工作(普通的Task.Run工作,以及ThreadHelper.JoinableTaskFactory.Run )。 The Andrew Arnott post linked earlier in my answer explains all of the choices. 在我的回答中早先链接的Andrew Arnott帖子解释了所有的选择。 You'll want to understand that fully since there are reasons to use some over others depending on the task. 你需要完全理解这一点,因为有理由根据任务使用一些而不是其他人。

Hope that helps! 希望有所帮助!

Task.Delay as used in your code returns a task that completes when you delay. Task.Delay中使用的Task.Delay返回一个在您延迟时完成的任务。 If you call it like that and ignore the result, it didn't do what you thought it did. 如果你这样称它并忽略结果,它就没有按照你的想法去做。 What you probably meant to is instead of calling Task.Factory.StartNew as you did, you want: 你可能想要的是,而不是像你一样调用Task.Factory.StartNew,你想要:

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

This says effectively "kick off a timer that'll wait 2 seconds, and then once it completes run the DoWork method on the UI thread. If more typing happens, then you can call cancellationTokenSource.Cancel() and just run again. 这有效地说“启动一个等待2秒的计时器,然后一旦它完成在UI线程上运行DoWork方法。如果发生更多的输入,那么你可以调用cancellationTokenSource.Cancel()并再次运行。

Also, I do have to ask about your type "CSharpRegionParser". 另外,我不得不问你的类型“CSharpRegionParser”。 If you need region information and you're on Visual Studio 2015, then you can get the syntax tree from Roslyn and you should be watching workspace change events rather than hooking LayoutChanged. 如果您需要区域信息并且您在Visual Studio 2015上,那么您可以从Roslyn获取语法树,您应该观察工作区更改事件而不是挂钩LayoutChanged。 You're also best off then structuring your system as a tagger/adornment manager pair as it might be clearer to write...it's not clear to me why you'd do parsing logic in LayoutChanged since LayoutChanged is something that happens during visual layout, including scrolling, resizing, etc. 您最好将系统构建为标记器/装饰管理器对,因为编写它可能更清楚......我不清楚为什么要在LayoutChanged中解析逻辑,因为LayoutChanged是在视觉布局中发生的事情,包括滚动,调整大小等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM