оформление чертежей 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 ответа:
Я не уверен, почему вы были отклонены, особенно потому, что это интересная проблема при работе с расширениями.
Итак, к вашей первой проблеме: Visual Studio имеет те же требования, что и WPF (с некоторыми дополнительными сложностями из-за его com-зависимости). Вы не можете обновить элемент пользовательского интерфейса, если вы не находитесь в главном потоке (UI). К сожалению, если вы нырнете прямо в него и подойдете к нему, используя стратегии, которые вы использовали бы для WPF, вы столкнетесь с целым другим миром проблем (в основном тупики).
Во-первых, освежите в памяти, как обрабатывать переключение с фоновых потоков на потоки пользовательского интерфейса в Visual Studio extension land. Я нашел асинхронное и многопоточное программирование в VS с использованием JoinableTaskFactory, чтобы быть полезным в объяснении.
Мне пришлось проделать нечто подобное с дорогостоящей операцией синтаксического анализа. Это было довольно прямолинейно.Мой синтаксический анализатор выполняется как часть экземпляра
IViewModelTagger
и использует следующую последовательность (грубо):
- он подписывается на событие
ITextBuffer.ChangedLowPriority
с обработчиком событийasync void
.- немедленно в огне, он отменяет любую операцию синтаксического анализа в процессе с помощью вызова
CancellationToken.Cancel()
. Маркер отмены передается во все, что его поддерживает (в Roslyn он поддерживается везде, где вы хотите, чтобы он был).- он начинает операцию синтаксического анализа, но перед ее запуском у меня есть вызов
Task.Delay(200, m_cancellationToken)
. Я 200 мс на основе моей скорости набора текста и того факта, что операции Roslyn имеютCancellationToken
перегрузки для чего-нибудь дорогого (моя работа по разбору тоже довольно легкая). YMMV.Я работаю с компонентами WPF, которые требуют потока пользовательского интерфейса совсем немного, и они перемешаны в
Чтобы справиться с этим, я сделал следующее:IViewModelTagger
иIWpfTextViewListener
. Они достаточно легкие, чтобы я мог пропустить их асинхронность, но на очень больших классах они могут повесить пользовательский интерфейс.
- на TextViewLayoutChanged я подписываюсь на событие
async void
обработчик.- я
Task.Run()
сначала выполняю дорогостоящие операции, предотвращая блокировку пользовательского интерфейса.- Когда я делаю окончательное создание элементов пользовательского интерфейса 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-это то, что происходит во время визуального макета, включая прокрутку, изменение размера и т. д.