Winforms 幸存的TPL、委托、线程和调用
我在多线程桌面/windows应用程序中面临严重的死锁问题。我担心我没有在非常异步的环境中使用正确的方法来处理委托。此外,即使我将事件“接收”到调用UI线程中,如果可能的话,我仍然必须在UI线程上调用以查看一些操作。下面,是细节 该应用程序基本上是在线文件存储服务用户的客户端。该服务通过REST调用公开功能。我首先为此类调用创建了托管代码包装DLL,允许.NET使用者创建此DLL的静态实例并调用函数。我将以文件上载操作为例 现在,在包装器中,这里是用于文件上载的公共接口:Winforms 幸存的TPL、委托、线程和调用,winforms,multithreading,c#-4.0,task-parallel-library,Winforms,Multithreading,C# 4.0,Task Parallel Library,我在多线程桌面/windows应用程序中面临严重的死锁问题。我担心我没有在非常异步的环境中使用正确的方法来处理委托。此外,即使我将事件“接收”到调用UI线程中,如果可能的话,我仍然必须在UI线程上调用以查看一些操作。下面,是细节 该应用程序基本上是在线文件存储服务用户的客户端。该服务通过REST调用公开功能。我首先为此类调用创建了托管代码包装DLL,允许.NET使用者创建此DLL的静态实例并调用函数。我将以文件上载操作为例 现在,在包装器中,这里是用于文件上载的公共接口: public Int3
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的实际UI应用程序将向操作发送进度委托和完成委托。现在,操作本身的主体是:
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()
引发两个事件:进度报告和完成。请注意,在引发事件时,我检查是否可以获得到调用线程的路由,并直接推送事件,以避免在UI上调用
现在,让我们看看我是如何在桌面客户端中使用上述所有功能的:
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上找到的扩展方法添加调用功能:
公共静态类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);
}
}
publicstaticvoidinvokeex(this T@this,Action-Action),其中T:Control{
如果(@this.invokererequired){
@调用(action,新对象[]{@this});
}
否则{
如果(!@this.IsHandleCreated)
回来
如果(@this.IsDisposed)
抛出新的ObjectDisposedException(“@this is disposed”);
行动(@this);
}
}
公共静态IAsyncResult BeginInvokeEx(this T@this,Action-Action)
其中T:控制{
返回@this.BeginInvoke((Action)(()=>@this.InvokeEx(Action));
}
公共静态void EndInvokeEx(this T@this,IAsyncResult结果)
其中T:控制{
@this.EndInvoke(result);
}
}
在我的代码中,我已经注释掉了调用,因为我认为我不需要这样做,因为所引发的事件即将发生。然而,我意识到我的UI根本没有做任何事情。因此,我添加了InvokeEx({code;})
,我的UI开始启动活动
现在,为什么我需要调用
如果我尝试从UI执行不同的操作,最终,我的UI会冻结,尽管应用程序仍能正常运行
我在网站上找到了一篇描述委托用法的老文章,我发现其中包含一个IAsyncResult
有人能告诉我哪里出了问题吗
更新:
好的,在UI上对调用代码进行了注释后,我就没有任何活动了。但是在使用this.InvokeEx
或在this.BeginInvokeEx
中包装工作时,我会得到UI更新,但过了一段时间后,出现了两个异常(按此顺序):
- 在创建窗口句柄之前,无法对控件调用Invoke或BeginInvoke
- 通过等待任务或访问其exception属性,未观察到任务的异常。结果,未观察到的异常被终结器线程重新抛出