在C#

时间:2015-09-05 04:51:30

标签: c# multithreading asynchronous parallel-processing amazon-sqs

我有一项服务需要尽快从Amazon SQS读取消息。我们期待繁忙的流量,我希望能够以每秒10K的消息读取数据。不幸的是,我目前大约有10条消息/秒。显然,我有工作要做。

这就是我正在使用的(转换为控制台应用程序以使测试更容易):

private static int _concurrentRequests;
private static int _maxConcurrentRequests;

public static void Main(string[] args) {
    _concurrentRequests = 0;
    _maxConcurrentRequests = 100;

    var timer = new Timer();
    timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    timer.Interval = 10;
    timer.Enabled = true;

    Console.ReadLine();
    timer.Dispose();
}

public static void OnTimedEvent(object s, ElapsedEventArgs e) {
    if (_concurrentRequests < _maxConcurrentRequests) {
        _concurrentRequests++;
        ProcessMessages();
    }
}

public static async Task ProcessMessages() {
    var manager = new MessageManager();
    manager.ProcessMessages();  // this is an async method that reads in the messages from SQS

    _concurrentRequests--;
}

我没有接近100个并发请求,并且它似乎没有每10毫秒触发OnTimedEvent

我不确定Timer是否是正确的做法。我对这种编码没有多少经验。我愿意在这一点上尝试任何事情。

更新

感谢calebboyd,我更接近实现目标。这是一些非常糟糕的代码:

private static SemaphoreSlim _locker;

public static void Main(string[] args) {
    _manager = new MessageManager();

    RunBatchProcessingForeverAsync();
}
private static async Task RunBatchProcessingForeverAsync() {
    _locker = new SemaphoreSlim(10, 10);
    while (true) {
        Thread thread = new Thread(new ParameterizedThreadStart(Process));
        thread.Start();
    }
}

private static async void Process(object args) {
    _locker.WaitAsync();
    try {
        await _manager.ProcessMessages();
    }
    finally {
        _locker.Release();
    }

}

我能够接近每秒读取相当数量的消息,但问题是我的ProcessMessages呼叫永远不会完成(或者可能会在很长一段时间后完成)。我想我可能需要限制我在任何时候运行的线程数。

有关如何改进此代码以便ProcessMessages有机会完成的任何建议吗?

2 个答案:

答案 0 :(得分:3)

因为您的MessageManager对象上的ProcessMessages方法没有被等待,我将假设它被绑定到它执行的同一个线程。仅仅将函数标记为async并不会将工作传递给一个新的线程。有了这个假设,这个代码实际上并没有用多个线程执行。您可以使用以下代码在更多的线程池中执行代码。

管理器对象可能无法处理并发使用。所以我在Task.Run lambda中创建它。这也可能是昂贵的,因此是不切实际的。

async Task RunBatchProcessingForeverAsync () {
    var lock = new SemaphoreSlim(initialCount: 10);
    while (true) {
        await lock.WaitAsync();
        Task.Run(() => {
            try {
                var manager = new MessageManager();
                manager.ProcessMessages();
            } finally {
                lock.Release();
            }
        });
    }
}

我暂时没有写过C#,但这应该同时,重复,永久地运行你的方法10次。

答案 1 :(得分:1)

正如@calebboyd建议的那样,您必须首先使您的线程异步。现在,如果你去这里 -    Where to use concurrency when calling an API,您将看到一个异步线程足以快速汇集网络资源。如果您能够在单个请求中从amazon获取多条消息,那么您的生产者线程(对亚马逊进行异步调用的线程)就可以了 - 它可以每秒发送数百个请求。这不会是你的瓶颈。但是,处理接收数据的继续任务将被传递给线程池。在这里你有机会获得瓶颈 - 假设每秒响应100个响应,每个响应包含100条消息(达到你的10K msgs / sec近似值)。每秒您有100个新任务,每个任务都需要您的线程处理100条消息。现在有两种选择:(1)这些消息的处理不受CPU限制 - 您只需将它们发送到您的数据库,或者(2),您执行CPU消耗计算,例如科学计算,序列化或一些繁重的业务逻辑。如果(1)是你的情况,那么瓶颈会向后推向你的DB。如果(2),则除了放大/缩小或优化计算之外别无选择。但是你的瓶颈可能不是生产线程 - 如果它的实现正确(参见上面的链接中的例子)。