BackgroundWorker 是保持 WCF/WPF 应用程序响应的唯一方法吗?
使用 C#、WCF、WPF 的客户端/服务器桌面应用程序。由于几乎每个操作都需要访问服务器(列表/创建/保存/删除/等),因此每个操作都有可能冻结整个 UI。这是一个简单实现的示例,调用 service.GetAll()
可能需要“很长”的时间(超过几百毫秒):(
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
vm.Users.Clear();
foreach (var user in service.GetAllUsers())
vm.Users.Add(user);
}
旁白:我很想知道为什么列表有AddRange
和ObservableCollection 没有。)
BackgroundWorker
来救援:(
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
var worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = false; });
e.Result = service.GetAllUsers();
};
worker.RunWorkerCompleted += (s, e) =>
{
vm.Users.Clear();
foreach (var user in (List<UserDto>)e.Result)
vm.Users.Add(user);
Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = true; });
};
worker.RunWorkerAsync();
}
旁白:上面的代码已被简化,但这就是它的要点。)
使用 BackgroundWorker
的代码完全按照我想要的方式工作。该应用程序始终保持响应,并且该按钮在通话期间处于禁用状态。然而,这意味着为用户可能执行的每个可能操作添加 15 行。
说不是这样的吧
Client/server desktop application using C#, WCF, WPF. Since pretty much every action is going to require a trip to the server (list/create/save/delete/etc), every action has the potential to freeze the entire UI. Here's an example of a naive implementation with a call to service.GetAll()
which could take a "long" time (more than a few hundred milliseconds):
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
vm.Users.Clear();
foreach (var user in service.GetAllUsers())
vm.Users.Add(user);
}
(Aside: I'd love to know why List has AddRange
and ObservableCollection doesn't.)
BackgroundWorker
to the rescue:
private void btnRefresh_Click(object sender, RoutedEventArgs e)
{
var worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = false; });
e.Result = service.GetAllUsers();
};
worker.RunWorkerCompleted += (s, e) =>
{
vm.Users.Clear();
foreach (var user in (List<UserDto>)e.Result)
vm.Users.Add(user);
Dispatcher.BeginInvoke((Action)delegate() { btnRefresh.IsEnabled = true; });
};
worker.RunWorkerAsync();
}
(Aside: code above has been simplified, but that's the gist of it.)
The code using BackgroundWorker
works exactly how I want it to. The app remains responsive at all times, and the button is disabled for the duration of the call. However, this means adding 15 lines to every possible action the user might make.
Say it ain't so.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
不,BackgroundWorker 不是唯一的方法,但它是一种方法。任何其他方式也将包括某种形式的异步构造,需要使用 Dispatch.BeginInvoke 来更新 UI。例如,您可以使用 ThreadPool:
如果这是一个重复模式(按钮将触发一些应异步执行的操作,在此过程中按钮被禁用),您可以将其包装到一个方法中:
...并调用它:
作为使用 ThreadPool 的选项,您也许还应该查看 任务并行库。
执行此操作时,您应该注意如何处理 UI 状态。例如,您有多个控件触发相同的操作,请确保在该操作期间所有控件都被禁用。
注意:这些只是简单的想法。该代码尚未经过测试,因此可能包含错误。它更应该被视为讨论材料而不是最终的解决方案。
No,
BackgroundWorker
is not the only way, but it's one way. Any other way will allso include some form of asynchronous construct with the need to useDispatch.BeginInvoke
to update the UI. You could for instance use theThreadPool
:If this is a recurring pattern (a button will trigger some action that should be performed asynchronously, with the button being disabled during the process) you can wrap this into a method:
...and call it:
As an option to using the
ThreadPool
, you should also perhaps look into the Task Parallel Library.When doing this you should pay attention to how you handle UI state. For instance of you have more than one control which triggers the same action, make sure that all of them are disabled during the action.
Note: these are just quick ideas. The code has not been tested so it may contain errors. It's more to be regarded as discussion material than finished solutions.
WCF 提供了异步进行所有服务调用的能力。在项目中创建服务引用时,添加服务引用对话框有一个“高级...”按钮。单击该按钮您将看到“生成异步操作”选项。如果单击该复选框,则每个操作都将以同步和异步方式生成。
例如,如果我有一个操作“DoSomething()”,那么在选中此框后,我将获得为调用 DoSomething() 和 DoSomethingAsync() 生成的代码。
您还将获得一个 Service.DoSomethingCompleted 事件,您可以在服务调用返回时使用该事件定义回调处理程序。
这是我们用来在不锁定 UI 的情况下进行服务调用的方法。
下面是 Microsoft 提供的一个相当复杂的示例,说明如何执行此操作:http:// msdn.microsoft.com/en-us/library/ms730059.aspx
WCF provides the ability to make all service calls asynchronously. When you create the service reference in your project, the add service reference dialog box has an "Advanced..." button. Clicking that you will see the option for "Generate Asynchronous operations". If you click that check-box then every operation will be generated in both a synchronous and asynchronous manner.
For example, if i have an operation "DoSomething()" then after checking this box i will get code generated for calling DoSomething() and DoSomethingAsync().
You will also get a Service.DoSomethingCompleted event that you can use to define a callback handler when the service call returns.
This is the method we used to make service calls without locking the UI.
Here is a rather complicated example provided by Microsoft on how to do this: http://msdn.microsoft.com/en-us/library/ms730059.aspx
这不是唯一的方法。我推荐
Task
(或Task
的更高级别抽象之一,例如Parallel
或PLINQ
)。我在我的博客回顾了异步后台操作的各种方法< /a>.
无论您选择哪种方法,当前的状态确实需要一些样板代码。 异步 CTP 显示了事情的发展方向 - 朝着更加干净的方向发展异步操作的语法。 (请注意,在撰写本文时,异步 CTP 与 VS SP1 不兼容)。
It is not the only way. I recommend
Task
(or one of the higher-level abstractions forTask
, such asParallel
orPLINQ
).I have a review of various approaches to asynchronous background operations on my blog.
The current state of things does require some boilerplate code, regardless of which approach you choose. The async CTP shows where things are going - towards a much, much cleaner syntax for asynchronous operations. (Note that - at the time of writing - the async CTP is incompatible with VS SP1).
好吧,BackgroundWorker 并不是您拥有的唯一选择,但为了完成您想要的任务,您仍然需要使用多个线程或异步操作,以便在等待长时间运行的操作完成时不会阻塞。
而且,由于 WPF 要求访问 UI 的所有代码都在同一线程上运行,因此当您在 UI 线程上调用或访问数据或代码时,您必须执行一些上下文切换。确保调用将在 WPF 中的 UI 线程上运行的方法是使用 调度程序类。
保持 UI 响应的另一个简单方法是在线程池中的线程上对工作项进行排队,这是使用 ThreadPool 类完成的。
一如既往,剥猫皮的方法不止一种,但要注意每种技术都有优点和缺点。例如,上面概述的方法可能很有用,因为它没有那么多代码开销,但在某些情况下它可能不是最有效的方法。
还可以使用其他选项,包括使用您所在类的 BeginXXX - EndXXX 方法正在使用,如果他们提供任何(例如 SqlCommand< /a> 类有 BeginExecuteReader EndExecuteReader)。或者,如果类有 XXXAsync 方法,则使用该方法。例如 System.Net.Sokets.Socket 类具有 ReceiveAsync 和 SendAsync。
Well, BackgroundWorker is not the only option you have but in order to accomplish what you want you still need to use multiple threads or asynchronous operations in order to not block while you wait for the long-running operations to finish.
And, because WPF requires that all code accessing the UI run on the same thread you do have to do some context switching when you call or access data or code on the UI thread. The way to ensure a call will run on the UI thread in WPF is to use the Dispatcher class.
Another simple way of keeping the UI responsive is to queue work item on a thread in the Thread Pool which is done using the ThreadPool class.
As always, there's more than one way to skin the cat but be aware that each technique has advantages and disadvantages. For instance the method outlines above could be useful because it doesn't have that much code overhead but it may not be the most efficient way in may cases.
Other options are available including using the BeginXXX - EndXXX methods of the classes you're using if they provide any (such as the SqlCommand class has BeginExecuteReader EndExecuteReader). Or, using the XXXAsync methods if the classes have that. For instance the System.Net.Sokets.Socket class has ReceiveAsync and SendAsync.
不,这不是唯一的选择。这个问题更多的是关于你如何设计你的应用程序。
您可以查看Windows Composite Applicaiton Framework (Prism),其中提供诸如 EventAggregator 之类的功能,可以帮助您发布应用程序范围的事件并在应用程序内的多个位置订阅它,并根据该事件采取操作。
此外,由于担心代码行过多,您可能希望以可以重构和重用尽可能多的代码的方式对应用程序架构进行分层。这样,您就可以让这些后台工作人员在一层中处理所有服务响应,同时您可以将 UI 层与其分离。
No this is not the only option. This question is more about how are you designing your application.
You can take a look at Windows Composite Applicaiton Framework (Prism), which provides features like EventAggregator which can help you publish application wide events out and subscribe it at multiple locations within your app and take actions based on that.
Also as far as being worried about having too many lines of code, you may want to layer your application architecture in such a way that you can refactor and reuse as much code as possible. This way you have these background workers handling all your service responses in one layer while you can leave your UI layer detached from it.
不,这不是唯一的方法,但它是更简单的方法之一(至少与设置自己的线程,或将任务推送到线程池线程并在完成时安排事件相比)。
No it's not the only way, but it is one of the simpler ones (at least compared to setting up your own thread, or pushing a task to a thread pool thread and arranging an event on completion).
您也许可以通过在某个地方编写一个静态方法来简化一点,该方法接受两个参数(回调函数)并为您处理其余部分,这样您就不必每次需要时都编写相同的样板板进行异步调用。
You might be able to simplify a little bit by writing a static method somewhere that takes two parameters, the callback functions, and handles the rest for you, that way you won't have to write all the same boiler plate every time you need to make an async call.
不,当然不是。
您可以创建原始 线程< /strong> 并执行时间,获取其中的代码,然后将代码分派到 UI 线程以访问/更新任何 UI 控件。有关 Disptacher 的更多信息 此处。
请参阅此,了解有关 Threads 中的线程的详细信息c#.
No, certaily not.
You can create a raw Thread and execute time taking code in it and then dispatch the code to the UI Thread to access/update any UI controls.More info on Disptacher here.
Refer to this for a great information about Threads in c#.