To explain my problem, I have prepared a simplified WPF application like this. There is a Window with a Button and this is the click event handler:
private async void Button_Click(object sender, RoutedEventArgs e)
{
IWorker worker = worker = new Worker();
await Task.Run(() => worker.DoSomething(10));
}
As you may have noticed I am using the "async void" construct which is not recommended but, if I understand correctly, it is allowed when it comes to an event handler. IWorker is a very simple interface and the Worker class is implementing it. Since DoSomething method is CPU bound and its operations can take several seconds, I decided to call it with Task.Run because I do not want to block the UI thread.
interface IWorker
{
int DoSomething(int i);
}
public class Worker : IWorker
{
public int DoSomething(int i)
{
// do long CPU-bound operation
return 42;
}
}
Till now the application works perfectly but I need to complicate the things a little. I have decided that there are two working modalities. The one I just implemented is called "normal", the other is "online". In "online" mode I want to use a different worker: RemoteWorker. It implements the IWorker interface as well but instead of performing CPU computations, it delegates a syncronous request to a server via socket.
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;
}
}
So here the new click event handler:
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));
}
The system works perfectly and I am satisfied because the same UI works whether I decide to perform the computation locally or remotely. But I am annoyed that RemoteWorker.DoSomething synchronously waits for the response on the socket. Perhaps the best solution would be to use async methods to read and write to the socket. In that case I should also change the method signature and IWorker interface like so:
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;
}
}
and maybe the button event should be:
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));
}
And at this point I come to my problem! I have two classes that implement the same interface but one has a 100% CPU method and the other has a 100% IO method. Is this the most elegant way to manage these requirements?
In theory Task.Run should only be used for 100% CPU methods while here I am also using it for 100% IO methods. I would like the code to remain as generic as possible so I prefer to avoid statements like "if type is Worker then do Task.Run" otherwise just await.