我正在尝试首次使用我需要编写的现有WinForms应用程序的Form应用TDD。我读过“C#中的敏捷原则,模式和实践”,这是我对TDD的大部分知识来源。
在本书中,作者建议使用稍微修改过的MVP形式进行UI开发,他将其称为Supervising Presenter,它使Presenter依赖于View和Model。我喜欢它导致Presenter中的代码看起来的方式。为了便于说明,这里有一个简单的例子:
class Presenter
{
Public IDomainApi _api;
Public IView _view;
...
public void PerformOperation()
{
bool userDecidesToPerformOperation = _view.PromptUserToConfirmOperation();
if( userDecidesToPerformOperation )
{
bool success = _api.PerformOperation();
if( success)
_view.AlertUserOperationSuccessful();
else
_view.AlertUserOperationFailed();
}
}
}
这一切都很棒,出于测试目的,我有一个模拟IDomainApi
和一个模拟IView
,我能够确保我的逻辑在控制器生命中是合理的。
对于实际应用程序_api
是IDomainApi
的真正实现,它可以在网络上运行,而_view
是实现Form
的{{1}}实例它具有所有必需的控件。
真实IView
执行的一些操作需要一些时间,因此我决定稍微修改Presenter方法以提醒用户正在进行操作。演示者被修改如下:
IDomainApi
我将新方法添加到“真实” public void PerformOperation()
{
bool userDecidesToPerformOperation = _view.PromptUserToConfirmOperation();
if( userDecidesToPerformOperation )
{
_view.NotifyPendingOperation("Performing operation ...");
bool success = _api.PerformOperation();
_view.PendingOperationCompleted();
if( success)
_view.AlertUserOperationSuccessful();
else
_view.AlertUserOperationFailed();
}
}
实现中,它只显示一个简单的对话框,其中包含传入的文本和进度条设置以指示活动(marqee设置)。遗憾的是,在测试时我发现对话框没有活动进度指示器,因为IView
正在运行的线程是UI线程(并阻止等待Presenter
完成)。
我尝试修改创建并使用_api.PerformOperation()
在单独的线程中调用Presenter
的代码,但这会导致UI无法正确呈现(呈现仅发生在UI线程而不是新的一个)。我可以看到这个问题的唯一解决方案涉及扩展Presenter
以暴露UI线程,以便我可以在该线程上调用适当的IView
方法,但这似乎会使Presenter代码变得丑陋会使它依赖于WinForms。有没有人找到更好的方法来处理这类事情?到目前为止,我的在线搜索主要产生关于TDD与Web的信息,有没有人知道如何做一些特定于WinForms的东西的好资源?
答案 0 :(得分:0)
您的问题的答案与TDD没什么关系。它更多的是关于异步方法调用。
让我们看一下Presenter
类中的这三行代码:
view.NotifyPendingOperation("...");
bool success = _api.PerformOperation();
_view.PendingOperationCompleted();
请记住,UI需要在UI线程上运行。那么,还有什么可以转移到后台工作线程,以便UI线程可以自由更新进度条?
假设演示者本身不执行长时间运行(> 0.4秒左右)操作,如果它在UI线程上运行则没有坏处。
然而,似乎_api.PerformOperation()
可能是一个长期运行的任务,因此将阻止UI线程。因此,我建议您在单独的线程上执行此方法调用。
有很多方法可以做到这一点,我无法详细解释它们。相反,我将为您提供一些起点:
使用System.ComponentModel.BackgroundWorker
在后台线程上执行您的API方法(通过DoWork
事件。在_view.PendingOperationCompleted
事件的处理程序中继续RunWorkerCompleted
...
...但要注意:当您为此事件订阅处理程序方法时,它将在后台线程上调用,您必须从该线程更新UI。在.BeginInvoke
表单上执行_view
(Winforms-only机制)以返回UI线程,或者查看System.Threading.SynchronizationContext
(通用解决方案,一开始并不容易理解,所以谷歌也有一些教程!)。
实施名为IDomainApi.PerformOperation
的{{1}}方法的异步版本,该版本遵循所谓的Event-based Async Pattern (EAP)。这种模式的优点是“完成”事件处理程序在同一个同步中被调用。发出方法调用的上下文(通常意味着在同一个线程上);这意味着与PerformOperationAsync
不同,您会自动在UI线程上回调,而不需要BackgroundWorker
来调用BeginInvoke
上的方法。
从.NET 4开始,使用任务并行库(TPL),即您的案例中的_view
类,以简化异步程序逻辑。 AFAIK,TPL建立在较低级别Task<bool>
和EAP(均在上文中提到)之上。
如果您正在编写C#5代码(例如,在Visual Studio 11中),您可以使用SynchronizationContext
和async
语言功能进一步简化异步程序逻辑在上面提到的TPL之上。
同样,这些只是指向如何使代码异步执行的可能方式的指针,从而释放UI线程来执行UI工作;如果其中任何一个不熟悉,请谷歌获取更多资源。进入更多细节将超出一个简单的答案。