两个线程上的交叉进程

时间:2015-06-16 19:27:32

标签: c# multithreading synchronization

我有一个可以用来访问某些表格数据的库。这个库是我访问数据的唯一方法。我正在使用的方法采用查询字符串和为每个结果行调用的回调。

目前,回调将每行加载到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;
}

1 个答案:

答案 0 :(得分:2)

如果我正确理解伪代码,你想要在后台线程上触发fetch操作,并在迭代器进入时产生结果,而不是在返回之前等待整个fetch操作完成。我会改变一些事情:

  • 如果要维护提取行的顺序,请使用队列而不是堆栈
  • 信令/阻塞只需要采用一种方式 - 返回行的线程产品需要等待提取线程将项添加到队列中。无需阻止提取线程

以下是使用TaskConcurrentQueueAutoResetEvent的简短示例:

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;
        }
    }
}

修改

使用BlockingCollection

实际上有更好的方法
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();
    }
}