Я сталкиваюсь с серьезной проблемой тупика в многопоточном приложении для рабочего стола / Windows.Боюсь, я не использую правильный подход к делегатам в очень асинхронной среде.Кроме того, несмотря на то, что я «вливаю» свои события в вызывающий поток пользовательского интерфейса, по возможности, мне все равно нужно вызывать поток пользовательского интерфейса, чтобы увидеть некоторые действия.Далее приведены подробности.
Приложение в основном является клиентом для пользователей онлайн-службы хранения файлов.Этот сервис предоставляет функциональность через вызовы REST.Сначала я создал библиотеку DLL управляемого кода для таких вызовов, которая позволяет потребителю .NET создавать статический экземпляр этой библиотеки DLL и вызывать функции.Я возьму в качестве примера операцию загрузки файла.
Теперь в обертке есть публичный интерфейс для загрузки файла:
public Int32 UploadFile(FileSystemObject FolderToUploadTo, FileInfo LocalFileAndPath, OperationProgressReportEventHandler onOperationProgressReport, FileSystemObjectUploadCompletedEventHandler onOperationCompleted) {
Int32 ReplyNumber = 0;
try {
var TheOperation = new UploadFileObjectOperation(FolderToUploadTo, LocalFileAndPath, _User.APIKey) {
onProgressReport = onOperationProgressReport,
onUploadCompleted = onOperationCompleted
};
//Add it to the pool of operations
OperationPool.Add(TheOperation);
//Start the operation through the factory
OperationFactory.StartNew(() => {
TheOperation.Start();
});
//Chain the *actual* TPL Task to flush after usage
TheOperation.InnerTask.ContinueWith(t => {
t.Dispose(); //Dispose the inner task
OperationPool.Remove(TheOperation); //Remove the operation from the pool
TheOperation = null; //Nullify the Operation
});
ReplyNumber = TheOperation.TaskId;
}
catch {
ReplyNumber = 0;
}
return ReplyNumber;
}
Как видите, фактическийПриложение пользовательского интерфейса, которое будет ссылаться на эту DLL, будет отправлять делегатов для выполнения и выполнения операции.Теперь тело самой операции:
public class UploadFileObjectOperation : BaseOperation, IDisposable {
//Store
public FileSystemObjectUploadCompletedEventHandler onUploadCompleted;
//Constructors
//Disposing stuff
protected override void PerformWork() {
try {
//Init the WebClient
UploadClient.UploadProgressChanged += (UploadProgressChanged_s, UploadProgressChanged_e) => {
//This is my event in base class being raised
ReportProgress(UploadProgressChanged_e.ProgressPercentage, UploadProgressChanged_e);
};
UploadClient.UploadFileCompleted += (UploadFileCompleted_s, UploadFileCompleted_e) => {
if (UploadFileCompleted_e.Error != null) {
throw new ApplicationException("Upload failed. " + UploadFileCompleted_e.Error.Message);
}
JObject JSONLiveObject = JObject.Parse(Encoding.UTF8.GetString(UploadFileCompleted_e.Result));
if (String.Compare((String)JSONLiveObject["status"], Constants._CONST_RESTRESPONSE_STATUS_VALUE_FAIL, false) == 0) {
throw new ApplicationException("Upload response failed. " + (String)JSONLiveObject["result"]["message"]);
}
//Eureka! Success! We have an upload!
//This is my event being raised
UploadTaskCompleted(new UploadFileObjectOperationEventArg {
Error = null,
ResultSource = OperationResultSource.Fresh,
Status = OperationExitStatus.Success,
TaskId = TaskId,
UploadedFileSystemObject = _UploadedFile
});
};
//Start the async upload
UploadClient.UploadFileAsync(AddressOfRESTURI, UploadingMethod, _FileToUpload.FullName);
}
catch (OperationCanceledException exp_Canceled) {
UploadTaskCompleted(new UploadFileObjectOperationEventArg {
Error = exp_Canceled,
ResultSource = OperationResultSource.Fresh,
Status = OperationExitStatus.Canceled,
TaskId = TaskId,
UploadedFileSystemObject = _UploadedFile
});
// To ensure that the calling code knows the task was canceled
//throw;
}
catch (Exception exp) {
UploadTaskCompleted(new UploadFileObjectOperationEventArg {
Error = exp,
ResultSource = OperationResultSource.Fresh,
Status = OperationExitStatus.Error,
TaskId = TaskId,
UploadedFileSystemObject = _UploadedFile
});
// If the calling code also needs to know.
//throw;
}
}
protected void UploadTaskCompleted(UploadFileObjectOperationEventArg arg) {
if (onUploadCompleted == null)
return;
//Sinking into calling UI thread, if possible
if (onUploadCompleted.Target is Control) {
Control targetForm = onUploadCompleted.Target as Control;
targetForm.Invoke(onUploadCompleted, new object[] { arg });
}
else {
onUploadCompleted(arg);
}
Status = OperationRunningStatus.Completed;
}
}
PerformWork()
вызывает два события: отчет о ходе выполнения и завершение.Обратите внимание, что во время вызова события я проверяю, можно ли получить маршрут к вызывающему потоку, и отправлять событие напрямую, чтобы избежать вызовов в пользовательском интерфейсе.
Теперь давайте посмотрим, как я использую все вышеперечисленное вклиент рабочего стола:
private void UploadFile(FileInfo DraggedFileInfo, FileSystemObject ParentDefination) {
SessionLifetimeStuff.APICore.UploadFile(ParentDefination, DraggedFileInfo,
(PercentageCompleted) => {
#region Progress
this.InvokeEx(f => {
UpdateTaskProgress(newTaskQueue.OID, PercentageCompleted.Progress, PercentageCompleted);
});
#endregion
}, (Result) => {
#region Completion
this.InvokeEx(f => {
switch (Result.Status) {
case OperationExitStatus.Success:
Console.WriteLine(String.Format("File: {0} uploaded to {1}", Result.UploadedFileSystemObject.DocumentFullname, Result.UploadedFileSystemObject.FolderId));
break;
case OperationExitStatus.Canceled:
DialogManager.ShowDialog(DialogTypeEnum.Warning, "Dropbox", "Upload canceled.", null, this);
break;
case OperationExitStatus.Error:
DialogManager.ShowDialog(DialogTypeEnum.Error, "Dropbox", "Upload failed.", Result.Error, this);
break;
}
});
#endregion
});
}
Я использую метод расширения, который я нашел в Stackoverflow для добавления функциональности Invoking: открытый статический класс InvokeExtensions {
public static void InvokeEx<T>(this T @this, Action<T> action) where T : Control {
if (@this.InvokeRequired) {
@this.Invoke(action, new object[] { @this });
}
else {
if (!@this.IsHandleCreated)
return;
if (@this.IsDisposed)
throw new ObjectDisposedException("@this is disposed.");
action(@this);
}
}
public static IAsyncResult BeginInvokeEx<T>(this T @this, Action<T> action)
where T : Control {
return @this.BeginInvoke((Action)(() => @this.InvokeEx(action)));
}
public static void EndInvokeEx<T>(this T @this, IAsyncResult result)
where T : Control {
@this.EndInvoke(result);
}
}
В моем коде у меня естьзакомментировал вызовы, как я, хотя тогда мне это и не нужно, так как поднимаемые события приходят в затонувшем состоянииОднако я понял, что мой пользовательский интерфейс вообще ничего не делал.Итак, я добавил InvokeEx({ code; })
и мой пользовательский интерфейс начал активную деятельность.
Теперь, зачем мне вызывать?
Если я пытаюсь выполнить другие операции из пользовательского интерфейса, в конечном итоге мой пользовательский интерфейс зависает, хотя приложение продолжает работать как обычно.
Iнашел старую статью в http://msdn.microsoft.com/en-us/library/ff649143.aspx#scag-ch06_topic4, в которой описывалось использование делегатов, и я вижу, что там задействован IAsyncResult.
Может кто-нибудь указать мне, где я здесь ошибаюсь?
Обновление: Хорошо, с кодом вызова, прокомментированным для пользовательского интерфейса, я вообще ничего не получаю.Но после использования this.InvokeEx
или упаковки работы в this.BeginInvokeEx
я получаю обновления пользовательского интерфейса, но через некоторое время происходят два исключения (в следующем порядке):
- Invoke или BeginInvoke не могутвызываться для элемента управления до тех пор, пока не будет создан дескриптор окна.
- Исключения (ий) Задачи не были обнаружены ни Ожиданием Задачи, ни доступом к ее свойству Исключение.В результате незамеченное исключение было переброшено потоком финализатора.