ClientSession에서 온 요청을 바로 실행하는 것이 아니라 job이라는 개념으로 캡슐화한 후 나중에 처리하는 방식으로 lock을 최소화 할 수 있다.
public interface IJob
{
void Execute();
}
public class Job : IJob // 매개변수 x
{
Action _action;
public Job(Action action)
{
_action = action;
}
public void Execute()
{
_action.Invoke();
}
}
public class Job<T1> : IJob // 매개변수 1개인 함수
{
Action<T1> _action;
T1 _t1; // 매개변수는 따로 관리
public Job(Action<T1> action, T1 t1)
{
_action = action;
_t1 = t1;
}
public void Execute()
{
_action.Invoke(_t1);
}
}
public class Job<T1, T2> : IJob
{
Action<T1, T2> _action;
T1 _t1;
T2 _t2;
public Job(Action<T1, T2> action, T1 t1, T2 t2)
{
_action = action;
_t1 = t1;
_t2 = t2;
}
public void Execute()
{
_action.Invoke(_t1, _t2);
}
}
public class JobSerializer
{
Queue<IJob> _jobQueue = new Queue<IJob>();
object _lock = new object();
bool _flush = false;
public void Push(Action action) { Push(new Job(action)); }
public void Push<T1>(Action<T1> action, T1 t1) { Push(new Job<T1>(action, t1)); }
public void Push<T1, T2>(Action<T1, T2> action, T1 t1, T2 t2) { Push(new Job<T1, T2>(action, t1, t2)); }
public void Push<T1, T2, T3>(Action<T1, T2, T3> action, T1 t1, T2 t2, T3 t3) { Push(new Job<T1, T2, T3>(action, t1, t2, t3)); }
public void Push(IJob job)
{
bool flush = false;
lock (_lock)
{
_jobQueue.Enqueue(job);
if (_flush == false) // 내가 처음으로 실행한 경우
flush = _flush = true;
}
if (flush)
Flush(); // job을 처리하는 중에 Push()가 호출되면 job만 던져놓음
}
void Flush()
{
while (true)
{
IJob job = Pop();
if (job == null)
return;
job.Execute();
}
}
IJob Pop()
{
lock (_lock)
{
if (_jobQueue.Count == 0)
{
_flush = false;
return null;
}
return _jobQueue.Dequeue();
}
}
}
Push를 하면서 jobQueue에 일감을 넣어줄 수 있다.
맨 처음 jobQueue에 일감을 넣어주는 경우, _flush가 false이기 때문에 true로 바꿔준 후 Flush() 함수를 호출하여 하나뿐인 일감을 꺼내서 그 일감을 실행할 것이다.
실행 중인 사이에 다른 일감이 10개씩 밀려 들어왔다면 그 10개의 일감은 _flush가 true이기 때문에 jobQueue에 넣어주는 것만 해주고 빠져나갈 것이고, 10개를 다 넣은 후 첫번째 일감이 실행 종료가 되면 11번째 일감이 왔을 때는 _flush가 다시 false이기 때문에, 자기자신을 포함한 11개의 일감을 Flush() 호출로 처리해 줄 것이다.