Выживший TPL, делегаты, потоки и вызовы - PullRequest
1 голос
/ 09 декабря 2011

Я сталкиваюсь с серьезной проблемой тупика в многопоточном приложении для рабочего стола / 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 не могутвызываться для элемента управления до тех пор, пока не будет создан дескриптор окна.
  • Исключения (ий) Задачи не были обнаружены ни Ожиданием Задачи, ни доступом к ее свойству Исключение.В результате незамеченное исключение было переброшено потоком финализатора.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...