用于CPU绑定和IO绑定操作的通用接口



为了解释我的问题,我准备了一个简化的WPF应用程序,如下所示。有一个带按钮的窗口,这是点击事件处理程序:

private async void Button_Click(object sender, RoutedEventArgs e)
{
IWorker worker = worker = new Worker();
await Task.Run(() => worker.DoSomething(10));
}

正如你可能已经注意到的;async void";构造,这是不推荐的,但如果我理解正确,当涉及到事件处理程序时,它是允许的。IWorker是一个非常简单的接口,Worker类正在实现它。由于DoSomething方法是CPU绑定的,其操作可能需要几秒钟,所以我决定用Task.Run调用它,因为我不想阻塞UI线程。

interface IWorker
{
int DoSomething(int i);
}
public class Worker : IWorker
{
public int DoSomething(int i)
{
// do long CPU-bound operation
return 42;
}
}

到目前为止,这个应用程序运行得很好,但我需要把事情稍微复杂一点。我决定有两种工作方式。我刚刚实现的一个被称为";"正常";,另一个是";在线";。在";在线";模式我想使用不同的工作程序:RemoteWorker。它也实现了IWorker接口,但不是执行CPU计算,而是通过套接字将同步请求委托给服务器。

public class RemoteWorker : IWorker
{
public int DoSomething(int i)
{
// it serializes the request and sends it to a server via socket.
// the server does the calculation and returns the value via socket.
// this method is waiting syncronously the output and then returns it.
return 42;
}
}

因此,这里有一个新的点击事件处理程序:

private async void Button_Click(object sender, RoutedEventArgs e)
{
IWorker worker;
if (mode == "normal")
worker = new Worker();
else
worker = new RemoteWorker();
await Task.Run(() => worker.DoSomething(10));
}

该系统工作得很完美,我很满意,因为无论我决定在本地还是远程执行计算,相同的UI都能工作。但是RemoteWorker.DoSomething同步等待套接字上的响应,这让我很恼火。也许最好的解决方案是使用异步方法对套接字进行读写。在这种情况下,我还应该更改方法签名和IWorker接口,如下所示:

interface IWorker
{
Task<int> DoSomething(int i);
}
public class Worker : IWorker
{
public Task<int> DoSomething(int i)
{
// do cpu long operation
return Task.FromResult(42);
}
}
public class RemoteWorker : IWorker
{
public async Task<int> DoSomething(int i)
{
// it serializes the request and sends it to a server via socket.
// the server does the calculation and returns the value via socket.
// this method is waiting asyncronously the output and then returns it.
return 42;
}
}

也许按钮事件应该是:

private async void Button_Click(object sender, RoutedEventArgs e)
{
IWorker worker;
if (mode == "normal")
worker = new Worker();
else
worker = new RemoteWorker();
await Task.Run(async () => await worker.DoSomething(10));
}

在这一点上,我遇到了我的问题!我有两个实现相同接口的类,但一个有100%CPU方法,另一个有100%IO方法。这是管理这些需求的最优雅的方式吗?

理论上,Task.Run应该只用于100%CPU方法,而在这里我也将其用于100%IO方法。我希望代码尽可能保持通用性,所以我更喜欢避免像";如果类型为Worker,则执行Task.Run";否则就等着吧。

Task.Run与同步和异步委托都能很好地工作。使用它从GUI应用程序的事件处理程序调用异步委托可以确保UI保持响应,即使异步委托实际上具有同步实现(如Worker.DoSomething方法或一些行为不端的API(。除非ThreadPool饱和,否则在Task.Run中包装真正异步的委托的开销可以忽略不计,在这种情况下,委托必须等待线程变为可用,然后才能调用异步委托。除非您通过启动多个并发的Parallel.ForEach操作或其他操作来大量使用ThreadPool,否则这种情况不太可能发生。因此,我的建议是采用您的第二种方法(基于异步的API(。

private async void Button_Click(object sender, RoutedEventArgs e)
{
IWorker worker = CreateWorker(mode);
await Task.Run(async () => await worker.DoSomethingAsync(10));
}

最新更新