C# 具有延迟更新的visual studio图形装饰

C# 具有延迟更新的visual studio图形装饰,c#,visual-studio,visual-studio-extensions,C#,Visual Studio,Visual Studio Extensions,我正在制作一个Visual Studio装饰扩展。我想更新装饰,如果没有用户输入至少2秒。所以我构建了一个worker并试图移除和添加装饰,但是VS说它无法更新,因为非ui线程调用了它。因此,我在没有线程的情况下等待,然后我的编辑器变得非常滞后(因为ui线程等待) 我想知道是否有一种方法可以用延迟更新来更新装饰。 绘制装饰是通过调用addAdorment()完成的,我找不到如何调用ui线程来绘制 下面是我的代码 internal async void OnLayoutChanged(ob

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

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

    }

代码中使用的
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是在可视化布局过程中发生的事情,包括滚动、调整大小等。

我不确定你为什么被否决,尤其是在处理扩展时,这是一个有趣的问题

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

首先,介绍一下如何在VisualStudioExtensionLand中处理从后台到UI线程的切换。我发现这有助于解释

我不得不用一个昂贵的解析操作来做类似的事情。这很直截了当

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

  • 它使用
    async void
    事件处理程序订阅
    ITextBuffer.ChangedLowPriority
    事件
  • 一旦启动,它就会通过调用
    CancellationToken.Cancel()
    取消正在进行的任何解析操作。取消令牌被传递到支持它的所有东西中(在Roslyn中,它在您希望它出现的任何地方都受到支持)
  • 它开始解析操作,但在开始之前,我有一个
    Task.Delay(200,m_cancellationToken)
    调用。基于我的打字速度和Roslyn的操作对任何昂贵的东西都有
    CancellationToken
    重载(我的解析工作也很轻)这一事实,我选择了200毫秒。YMMV
  • 我使用的WPF组件需要相当多的UI线程,它们混合在
    IViewModelTagger
    IWpfTextViewListener
    中。它们足够轻量级,我可以跳过异步,但在非常大的类上,它们可以挂起UI

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

  • 在TextViewLayoutChanged上,我使用
    异步void
    事件处理程序进行订阅
  • I
    Task.Run()
    首先执行昂贵的操作,防止UI被阻塞
  • 当我最终创建WPF UI元素并将它们添加为装饰最终完成(以及SDK中需要它的两个操作)时,我
    等待ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()
    以获取UI线程
  • 我提到了“其他SDK操作”,这很重要。在SDK中,除了主线程之外,还有几件事情是无法在其他线程上完成的(现在内存不足,但如果在后台线程上访问TextView的某些部分,则会特别失败,而且不会一致)

    有更多选项用于执行UI线程的工作(普通
    Task.Run
    works以及
    ThreadHelper.JoinableTaskFactory.Run
    )。在我的回答前面链接的Andrew Arnott帖子解释了所有的选择。您需要充分理解这一点,因为根据任务的不同,有理由使用某些方法而不是其他方法


    希望有帮助

    在后台任务中等待,调用主线程进行更新我尝试了,但失败了。我需要的是一个调度员。