如何进行多个异步调用并从找到匹配记录的第一个调用返回数据,然后停止其余的异步调用

时间:2019-06-28 16:34:41

标签: c# asynchronous async-await task-parallel-library

我需要通过遍历连接字符串来进行多个数据库调用。数据库之间只有1条匹配记录,如果我找到一条匹配记录,则可以返回数据并取消其他异步调用。

using (var Contexts = instContextfactory.GetContextList())
{
    foreach(var context in Contexts.GetContextList())
    {    
        // how do I make all the calls and return data from the first call that finds data and continue with further process.(don't care about other calls if any single call finds data.           
        context.Insurance.GetInsuranceByANI(ani);
    }
}

GetInsuranceByANI

public Task<IEnumerable<Insurance>> GetInsuranceByANI(string ani)
{
    using (ITransaction transaction = Session.Value.BeginTransaction())
    {
        transaction.Rollback();
        IDbCommand command = new SqlCommand();
        command.Connection = Session.Value.Connection;

        transaction.Enlist(command);

        string storedProcName = "spGetInsurance";

        command.CommandText = storedProcName;
        command.Parameters.Add(new SqlParameter("@ANI", SqlDbType.Char, 0, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, ani));

        var rdr = command.ExecuteReader();
        return Task.FromResult(MapInsurance(rdr));
    }
}

例如:我正在遍历5个(a,b,c,d,e)不同的数据库连接字符串。我需要对所有5个数据库进行asyn调用。如果我在db:b中找到匹配的记录,那么我可以返回该数据并继续进行下一步,并且可以停止调用其他dbs

2 个答案:

答案 0 :(得分:2)

如何立即获取返回值。不允许流量向前移动并破坏循环。

using (var Contexts = instContextfactory.GetContextList())
    {
           foreach(var context in Contexts.GetContextList())
           {    
               // how do I make all the calls and return data from the first call that finds data and continue with the further process.(don't care about other calls if any single call finds data.           
                var result = await context.Insurance.GetInsuranceByANI(ani);

                if(result.Any())
                {
                    return result.First();
                }
           }
    }

答案 1 :(得分:0)

为使事情变得简单,您应该首先将GetInsuranceByANI方法改回同步状态。我们将生成任务以稍后异步调用它。

public IEnumerable<Insurance> GetInsuranceByANI(string ani)
{
    using (ITransaction transaction = Session.Value.BeginTransaction())
    {
        transaction.Rollback();
        IDbCommand command = new SqlCommand();
        command.Connection = Session.Value.Connection;

        transaction.Enlist(command);

        string storedProcName = "spGetInsurance";

        command.CommandText = storedProcName;
        command.Parameters.Add(new SqlParameter("@ANI", SqlDbType.Char, 0, ParameterDirection.Input, false, 0, 0, null, DataRowVersion.Default, ani));

        var rdr = command.ExecuteReader();
        return MapInsurance(rdr);
    }
}

现在实现了异步搜索所有数据库的方法。我们将在线程池线程中为每个数据库创建一个任务。这是有争议的,但是我们正在努力使事情变得简单。我们还实例化了CancellationTokenSource,并将其Token传递给所有Task.Run方法。这只会确保在获得结果之后,不再有其他任务要开始。如果线程池中的可用线程多于要搜索的数据库,则所有任务将立即开始,并且取消令牌实际上不会取消任何操作。换句话说,无论如何,所有已启动的查询都将完成。这显然是浪费资源,但是我们再次尝试使事情保持简单。

启动任务后,我们进入一个循环,等待下一个任务完成(使用方法Task.WhenAny)。如果找到结果,我们将取消令牌并返回结果。如果未找到结果,则继续进行下一个结果的循环。如果所有任务都完成了,但仍然没有结果,我们将返回null。

async Task<IEnumerable<Insurance>> SearchAllByANI(string ani)
{
    var tasks = new HashSet<Task<IEnumerable<Insurance>>>();
    var cts = new CancellationTokenSource();
    using (var Contexts = instContextfactory.GetContextList())
    {
        foreach (var context in Contexts.GetContextList())
        {
            tasks.Add(Task.Run(() =>
            {
                return context.Insurance.GetInsuranceByANI(ani);
            }, cts.Token));
        }
    }
    while (tasks.Count > 0)
    {
        var task = await Task.WhenAny(tasks);
        var result = await task;
        if (result != null && result.Any())
        {
            cts.Cancel();
            return result;
        }
        tasks.Remove(task);
    }
    return null;
}

用法示例:

IEnumerable<Insurance> result = await SearchAllByANI("12345");
if (result == null)
{
    // Nothing fould
}
else
{
    // Do something with result
}
相关问题