оформление чертежей visual studio с ленивым обновлением


Я делаю расширение для украшения Visual Studio. Я хочу обновить украшения, если нет пользовательского ввода по крайней мере 2 секунды. Поэтому я построил рабочий и попытался удалить и добавить украшение, но VS говорит, что оно не может быть обновлено, потому что его вызвал поток без пользовательского интерфейса. Поэтому я ждал без потока, тогда мой редактор действительно отстает (потому что поток пользовательского интерфейса ждет)

Я хочу знать, есть ли способ обновить украшения с помощью lazy update. Рисование украшения выполняется вызовом AddAdornment (), и я не могу найти, как вызвать поток пользовательского интерфейса для рисования.

Ниже приведен мой код

    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 3

2 ответа:

Я не уверен, почему вы были отклонены, особенно потому, что это интересная проблема при работе с расширениями.

Итак, к вашей первой проблеме: Visual Studio имеет те же требования, что и WPF (с некоторыми дополнительными сложностями из-за его com-зависимости). Вы не можете обновить элемент пользовательского интерфейса, если вы не находитесь в главном потоке (UI). К сожалению, если вы нырнете прямо в него и подойдете к нему, используя стратегии, которые вы использовали бы для WPF, вы столкнетесь с целым другим миром проблем (в основном тупики).

Во-первых, освежите в памяти, как обрабатывать переключение с фоновых потоков на потоки пользовательского интерфейса в Visual Studio extension land. Я нашел асинхронное и многопоточное программирование в VS с использованием JoinableTaskFactory, чтобы быть полезным в объяснении.

Мне пришлось проделать нечто подобное с дорогостоящей операцией синтаксического анализа. Это было довольно прямолинейно.

Мой синтаксический анализатор выполняется как часть экземпляра IViewModelTagger и использует следующую последовательность (грубо):

  1. он подписывается на событие ITextBuffer.ChangedLowPriority с обработчиком событий async void.
  2. немедленно в огне, он отменяет любую операцию синтаксического анализа в процессе с помощью вызова CancellationToken.Cancel(). Маркер отмены передается во все, что его поддерживает (в Roslyn он поддерживается везде, где вы хотите, чтобы он был).
  3. он начинает операцию синтаксического анализа, но перед ее запуском у меня есть вызов Task.Delay(200, m_cancellationToken). Я 200 мс на основе моей скорости набора текста и того факта, что операции Roslyn имеют CancellationToken перегрузки для чего-нибудь дорогого (моя работа по разбору тоже довольно легкая). YMMV.

Я работаю с компонентами WPF, которые требуют потока пользовательского интерфейса совсем немного, и они перемешаны в IViewModelTagger и IWpfTextViewListener. Они достаточно легкие, чтобы я мог пропустить их асинхронность, но на очень больших классах они могут повесить пользовательский интерфейс.

Чтобы справиться с этим, я сделал следующее:
  1. на TextViewLayoutChanged я подписываюсь на событие async void обработчик.
  2. я Task.Run() сначала выполняю дорогостоящие операции, предотвращая блокировку пользовательского интерфейса.
  3. Когда я делаю окончательное создание элементов пользовательского интерфейса WPF и добавляю их в качестве завершающих украшений (вместе с парой операций в SDK, которые требуют этого), я await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync() получаю поток пользовательского интерфейса.

Я упомянул "другие операции SDK", это важно. Есть несколько вещей, которые вы не можете сделать в SDK ни на чем, кроме основного потока (память подводит меня сейчас, но части TextView в частности не будет работать, и не последовательно, если они доступны в фоновых потоках).

Есть больше возможностей для выполнения работы вне потока пользовательского интерфейса (обычные Task.Run работы, а также ThreadHelper.JoinableTaskFactory.Run). Сообщение Эндрю Арнотта, связанное ранее с моим ответом, объясняет все варианты выбора. Вы захотите понять это полностью, так как есть причины использовать одни над другими в зависимости от задачи.

Надеюсь, это поможет!

Task.Delay используемый в коде возвращает задачу, которая завершается при задержке. Если вы называете его так и игнорируете результат, он не сделал того, что вы думали. То, что вы, вероятно, имели в виду, вместо вызова задачи.Фабрика.Начните заново, как вы это сделали, вы хотите:

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

Это эффективно говорит: "запустите таймер, который будет ждать 2 секунды, а затем, как только он завершится, запустите метод DoWork в потоке пользовательского интерфейса. Если набирается больше текста, то вы можете вызвать cancellationTokenSource.Отменить () и просто запустить снова.

Кроме того, я должен спросить о вашем типе "CSharpRegionParser". Если вам нужна информация о регионе и вы находитесь в Visual Studio 2015, то вы можете получить синтаксическое дерево от Roslyn, и вы должны наблюдать за событиями изменения рабочей области, а не подключать LayoutChanged. Вам также лучше всего структурировать свою систему как пару tagger / adornment manager, поскольку это может быть более понятным для write...it мне не ясно, почему вы делаете синтаксический анализ логики в LayoutChanged, так как LayoutChanged-это то, что происходит во время визуального макета, включая прокрутку, изменение размера и т. д.