Ответ 1
По-видимому, объект Paragraph
(и его под-объекты) требует сходства потоков. То есть он не является потокобезопасным и предназначен для использования только в том же потоке, который был создан.
Предположительно, вы вызываете RunWorkerAsync
из основного потока пользовательского интерфейса, а затем вызываете worker_RunWorkerCompleted
. Таким образом, вы получаете доступ к экземпляру Paragraph
в основном потоке после завершения работы. Однако он был создан на фоне рабочего потока, внутри process.Execute
. Вот почему вы получаете исключение InvalidOperationsException
, когда вы касаетесь его из основного потока.
Если вышеуказанное понимание проблемы верное, вы должны, вероятно, отказаться от BackgroundWorker
. Не имеет смысла использовать фоновый поток для запуска цикла for
, единственной целью которого было бы мобилизовать обратные вызовы в поток пользовательского интерфейса через Dispatcher.Invoke
. Это добавило бы дополнительные накладные расходы.
Вместо этого вы должны запускать фоновый режим в потоке пользовательского интерфейса, по частям. Вы можете использовать DispatcherTimer
для этого, или вы можете удобно запустить его с помощью async/await (таргетинг на .NET 4.5 или .NET 4.0 с Microsoft.Bcl.Async
и VS2012 +):
public async Task Execute(string[] folderContent, CancellationToken token)
{
this.formatedFilenames = new Paragraph();
if (folderContent.Length > 0)
{
for (int f = 0; f < folderContent.Length; ++f)
{
token.ThrowIfCancellationRequested();
// yield to the Dispatcher message loop
// to keep the UI responsive
await Dispatcher.Yield(DispatcherPriority.Background);
this.formatedFilenames.Inlines.Add(
new Run(folderContent[f] + Environment.NewLine));
// don't do this: Thread.Sleep(500);
// optionally, throttle it;
// this step may not be necessary as we use Dispatcher.Yield
await Task.Delay(500, token);
}
}
}
Там есть какая-то кривая обучения, когда дело доходит до async/await
, но это, безусловно, стоит того. aync-wait tag wiki перечисляет некоторые полезные ресурсы, для начала.
Чтобы вызывать реализацию async
Execute
, как указано выше, вам необходимо включить "Async all the way" править. Обычно это означает, что вы вызываете Execute
из обработчика событий или команд верхнего уровня, который также является async
и await
его результатом, например:
CancellationTokenSource _cts = null;
async void SomeCommand_Executed(object sender, RoutedEventArgs e)
{
if (_cts != null)
{
// request cancellation if already running
_cts.Cancel();
_cts = null;
}
else
{
// start a new operation and await its result
try
{
_cts = new CancellationTokenSource();
await Execute(this.folderContent, _cts.Token);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Также можно использовать шаблон события, чтобы сделать поток кода более похожим на исходный сценарий, в котором вы обрабатываете RunWorkerCompleted
:
// fire ExecuteCompleted and pass TaskCompletedEventArgs
class TaskCompletedEventArgs : EventArgs
{
public TaskCompletedEventArgs(Task task)
{
this.Task = task;
}
public Task Task { get; private set; }
}
EventHandler<TaskCompletedEventArgs> ExecuteCompleted = (s, e) => { };
CancellationTokenSource _cts = null;
Task _executeTask = null;
// ...
_cts = new CancellationTokenSource();
_executeTask = DoUIThreadWorkLegacyAsync(_cts.Token);
// don't await here
var continutation = _executeTask.ContinueWith(
task => this.ExecuteCompleted(this, new TaskCompletedEventArgs(task)),
_cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.FromCurrentSynchronizationContext());
В этом случае вы должны явно проверить свойства объекта Task
, такие как Task.IsCancelled
, Task.IsFaulted
, Task.Exception
, Task.Result
внутри вашего обработчика событий ExecuteCompleted
.