Вы не можете получить как простое наследование (подпись и перехват), так и поддержку асинхронных операций в своем коде.
Эти две цели взаимоисключающие.
Наследники должны знать о механизмах обратного вызова либо в прямом (Задачи, асинхронно) или косвенно (события, функции обратного вызова, Auto(Manual)ResetEvents или другие конструкции синхронизации). Некоторые из них новые, некоторые старые. И сложно сказать, какой из них будет лучше для конкретного случая использования.
Ну, может показаться, что есть простой способ с многопоточным кодом, но что, если ваш DoRealJob
на самом деле будет работать в другом процесс или используйте постановка в очередь удаленных заданий, поэтому реальное задание будет выполняться даже за пределами вашего приложения?
So:
- Если вы действительно считаете, что ваш класс будет использоваться в качестве основы для какого-то асинхронного работника, то вы должны спроектировать его соответствующим образом.
- Если нет - не переусердствуйте. Вы не можете рассматривать любой возможный сценарий. Просто задокументируйте свой класс достаточно хорошо, и я сомневаюсь, что кто-нибудь попытается реализовать
DoRealJob
асинхронно, особенно если вы назовете его DoRealJobSynchronously
. Если кто-то попытается это сделать, то в этом случае ваша совесть может быть первозданно чиста.
ИЗМЕНИТЬ:
Как вы думаете, было бы правильно, если бы я предоставил обе версии, синхронную и асинхронную, DoRealJob и флаг IsAsynchronous, чтобы я мог решить, какую из них вызывать?
Как я уже сказал, я не знаю ваших реальных сценариев использования. И нереально считать, что конструкция сможет эффективно справиться со всеми ними.
Также есть два очень важных вопроса, которые относятся к вашему общему Worker
классу и его DoJob
методу:
1) Вы должны определить, хотите ли вы, чтобы метод DoJob
был синхронным или асинхронным, или вы хотите иметь как синхронную, так и асинхронную версии? Это не имеет прямого отношения к вашему вопросу, но по-прежнему является очень важным проектным решением, поскольку оно окажет большое влияние на вашу объектную модель. Этот вопрос можно было бы перефразировать так:
Вы хотите, чтобы метод DoJob
блокировал любые действия после его вызова, пока он не выполнит свою работу, или вы хотите вызвать его как метод StartJob
, который просто запустит реальную обработку, но другие механизмы должны уведомить вас, когда задание завершено (или остановить его вручную):
//----------------Sync worker--------------------------
SyncWorker syncWorker = CreateSyncStringWriter("The job is done");
Console.WriteLine("SyncWorker will be called now");
syncWorker.DoJob(); // "The job is done" is written here
Console.WriteLine("SyncWorker call ended");
//----------------Async worker--------------------------
Int32 delay = 1000;
AsyncWorker asyncWorker = CreateAsyncStringWriter("The job is done", delay);
Console.WriteLine("AsyncWorker will be called now");
asyncWorker.StartDoJob(); // "The job is done" won't probably be written here
Console.WriteLine("AsyncWorker call ended");
// "The job is done" could be written somewhere here.
2) Если вы хотите, чтобы DoJob
был асинхронным (или имел асинхронную версию), вам следует подумать, хотите ли вы иметь какие-то механизмы, которые будут уведомлять, когда DoJob завершит обработку - Шаблоны асинхронного программирования , либо для вас абсолютно неважно, когда он заканчивается и заканчивается ли вообще.
Итак:
У вас есть ответы на эти два вопроса?
- Если да - то хорошо.
- Если нет - доработайте и учтите ваши требования.
- Если вы все еще не уверены, придерживайтесь простых методов синхронизации.
Однако, если вы считаете, что вам нужна некоторая асинхронная инфраструктура, то, учитывая, что это C# 3.0, вам следует использовать Модель асинхронного программирования.
Почему этот, а не основанный на событии? Поскольку интерфейс IAsyncResult, несмотря на его громоздкость, довольно общий и может быть легко использоваться в модель, упрощающая будущий переход на более поздние версии .NET.
Это будет что-то вроде:
/// <summary>
/// Interface for both the sync and async job.
/// </summary>
public interface IWorker
{
void DoJob();
IAsyncResult BeginDoJob(AsyncCallback callback);
public void EndDoJob(IAsyncResult asyncResult);
}
/// <summary>
/// Base class that provides DoBefore and DoAfter methods
/// </summary>
public abstract class Worker : IWorker
{
protected abstract void DoBefore();
protected abstract void DoAfter();
public IAsyncResult BeginDoJob(AsyncCallback callback)
{
return new Action(((IWorker)this).DoJob)
.BeginInvoke(callback, null);
}
//...
}
public abstract class SyncWorker : Worker
{
abstract protected void DoRealJobSync();
public void DoJob()
{
DoBefore();
DoRealJobSync();
DoAfter();
}
}
public abstract class AsyncWorker : Worker
{
abstract protected IAsyncResult BeginDoRealJob(AsyncCallback callback);
abstract protected void EndDoRealJob(IAsyncResult asyncResult);
public void DoJob()
{
DoBefore();
IAsyncResult asyncResult = this.BeginDoRealJob(null);
this.EndDoRealJob(asyncResult);
DoAfter();
}
}
P.S. Этот пример неполный и не проверен.
P.P.S. Вы также можете рассмотреть возможность использования делегатов вместо абстрактных (виртуальных) методов для выражения своих заданий:
public class ActionWorker : Worker
{
private Action doRealJob;
//...
public ActionWorker(Action doRealJob)
{
if (doRealJob == null)
throw new ArgumentNullException();
this.doRealJob = doRealJob;
}
public void DoJob()
{
this.DoBefore();
this.doRealJob();
this.DoAfter();
}
}
DoBefore
и DoAfter
могут быть выражены аналогичным образом.
PPPS: делегат Action
— это конструкция версии 3.5, поэтому вам, вероятно, придется определить собственный делегат, который принимает нулевые параметры и возвращает значение void.
public delegate void MyAction()
person
Eugene Podskal
schedule
06.07.2014
DoRealJob
должен быть синхронным, чтобы ваш SDK работал правильно. В противном случае вы можете предоставить альтернативный класс Async, который принимает обратный вызов. - person Darin Dimitrov   schedule 06.07.2014