我有一个可以用来访问某些表格数据的库。这个库是我访问数据的唯一方法。我正在使用的方法采用查询字符串和为每个结果行调用的回调。
目前,回调将每行加载到List中,然后返回List。我想使用迭代器模式,但我对数据的唯一访问是通过这个回调方法。
有没有办法可以在第二个线程上运行查询/回调并使用迭代器代码交换该代码? Psudocode:
IEnumerable<Row> QueryData(string queryString)
{
var callerLock = create new sync lock;
var callbackLock = create new sync lock;
var rows = create new stack of rows with capacity 1;
var qthread = create new thread with QueryCallback(queryString, callerLock, callbackLock, rows);
start qthread;
while (qthread is running)
{
signal callbackLock;
wait for callerLock;
if stack is empty
break;
var row = pop from rows;
yield return row;
}
}
void QueryCallback(string queryString, lock callerLock, lock callbackLock, Stack<Row> rows)
{
DoQueryWithCallback(queryString, row =>
{
wait for callbackLock;
push row to rows;
signal callerLock;
});
signal callerLock;
}
我已尝试使用.NET Framework中提供的大多数锁实现此功能,但它们都不起作用。我记得尝试过Semaphore,SemaphoreSlim,AutoResetEvent,ManualResetEvent和Mutex。
P.S。:DoQueryWithCallback来自图书馆。它是一个本机库(ILSpy / Reflector / etc无法反编译)。我想这个函数看起来像这样:
long DoQueryWithCallback(string queryString, Callback rowCallback)
{
do some setup;
Row row;
while (next(out row))
rowCallback(row);
do some teardown;
}
答案 0 :(得分:2)
如果我正确理解伪代码,你想要在后台线程上触发fetch操作,并在迭代器进入时产生结果,而不是在返回之前等待整个fetch操作完成。我会改变一些事情:
以下是使用Task
,ConcurrentQueue
和AutoResetEvent
的简短示例:
public IEnumerable<Row> GetRows(string query)
{
using (var resetEvent = new AutoResetEvent(false))
{
var rows = new ConcurrentQueue<Row>();
var queryTask = Task.Run(() => DoQueryWithCallback(query, r =>
{
rows.Enqueue(r);
resetEvent.Set();
}));
queryTask.ContinueWith(t => resetEvent.Set()); // This ensures that queryTask.IsCompleted will be true in the while loop below
while (resetEvent.WaitOne() && !queryTask.IsCompleted)
{
Row row;
while (rows.TryDequeue(out row))
yield return row;
}
}
}
修改强>
实际上有更好的方法public IEnumerable<Row> GetRows(string query)
{
using (var rows = new BlockingCollection<Row>())
{
Task.Run(() =>
{
DoQueryWithCallback(query, r => rows.Add(r));
rows.CompleteAdding();
});
while (!rows.IsCompleted)
yield return rows.Take();
}
}