Под окнами Debug> в Visual Studio вы можете посмотреть на окно потоков, окно стека вызовов и окно задач paralell.
Когда отладчик останавливается для получаемого исключения, вы можете посмотреть в окне callstack, чтобы увидеть, какой поток выполняет вызов и откуда этот поток приходит.
-editна основе опубликованного скриншота -
вы можете щелкнуть правой кнопкой мыши в стеке вызовов и выбрать «показать внешний код», чтобы точно увидеть, что происходит в стеке, но «внешний код» означает «где-то в структуре», поэтомуэто может или не может быть полезно (хотя обычно я нахожу это интересным :))
Из вашего скриншота также видно, что вызов выполняется из потока пула потоков.Если вы посмотрите в окно потоков, то увидите, что у одного из них есть желтая стрелка.Это поток, в котором мы сейчас выполняем, и где создается исключение.Название этого потока - «Рабочий поток», и это означает, что он приходит из пула потоков.
Как уже отмечалось, вы должны вносить любые изменения в ваш пользовательский интерфейс из пользовательского потока.Например, вы можете использовать «Invoke» на элементе управления, см. @CodeInChaos awnser.
-edit2-
Я прочитал ваши комментарии к @CodeInChaos awnser, и вот один из способов сделать это более похожим на TPL способом: Прежде всего, вам нужно получить экземплярTaskScheduler
, который будет запускать задачи в потоке пользовательского интерфейса.Вы можете сделать это, объявив TaskScheduler
в своем пользовательском интерфейсе с именем, например, uiScheduler
и в конструкторе, установив его на TaskScheduler.FromCurrentSynchronizationContext();
Теперь, когда у вас есть это, вы можете сделать новую задачу, котораяобновляет пользовательский интерфейс:
Task.Factory.StartNew( ()=> String.Format("Processing {0} on thread {1}", filename,Thread.CurrentThread.ManagedThreadId),
CancellationToken.None,
TaskCreationOptions.None,
uiScheduler ); //passing in our uiScheduler here will cause this task to run on the ui thread
Обратите внимание, что мы передаем планировщик задач в задачу при запуске.
Существует также второй способ сделать это, использующий apis TaskContinuation.Однако мы больше не можем использовать Paralell.Foreach, но мы будем использовать обычный foreach и задачи.ключ в том, что задача позволяет вам запланировать другую задачу, которая будет выполняться после выполнения первой задачи.Но вторая задача не обязательно должна выполняться на том же планировщике , и это очень полезно для нас прямо сейчас, поскольку мы хотим выполнить некоторую работу в фоновом режиме и затем обновить пользовательский интерфейс:
foreach( var currectFile in files ) {
Task.Factory.StartNew( cf => {
string filename = Path.GetFileName( cf ); //make suse you use cf here, otherwise you'll get a race condition
using( Bitmap bitmap = new Bitmap( cf ) ) {// again use cf, not currentFile
bitmap.RotateFlip( RotateFlipType.Rotate180FlipNone );
bitmap.Save( Path.Combine( newDir, filename ) );
return string.Format( "Processed {0} on thread {1}", filename, Thread.CurrentThread.ManagedThreadId );
}
}, currectFile, cancelToken.Token ) //we pass in currentFile to the task we're starting so that each task has their own 'currentFile' value
.ContinueWith( t => this.Text = t.Result, //here we update the ui, now on the ui thread..
cancelToken.Token,
TaskContinuationOptions.None,
uiScheduler ); //..because we use the uiScheduler here
}
То, что мы делаем здесь, - это создание новой задачи в каждом цикле, которая будет выполнять эту работу и генерировать сообщение, затем мы подключаем другую задачу, которая фактически обновит пользовательский интерфейс.
Вы можете прочитатьподробнее о ContinueWith и продолжениях здесь