2013-12-21 50 views
1

我有一个自动投注BOT。同时运行代码处理多线程应用程序的最佳方法

我使用Windows服务和计时器在其自己的线程中每隔30秒创建一个作业,该线程需要从数据库中下注,循环并放置它们。

但是在某些情况下,当作业太长(超过30秒)时,我可以使用与作业相同的BetPK(唯一ID)放置两次相同的下注,开始线程。

我使用C#,.NET 4,VS 2012

目前,我设置表格中的“锁定”标志时,该作业投注运行,然后取消设置它完成。所以如果另一个作业运行并且作业被锁定,它将尽快返回。但是,这依赖于数据库和网络流量。

什么是C#中最好的方式,以防止由定时器线程启动的作业与先前启动的线程发生冲突。我想我可以在产生线程的服务控制器中设置一个标志,所以如果一个工作正在运行,另一个不会产生。

但是,我想了解正确的方式来处理像这样的多重威胁冲突。由于在同一时间投注2次投注,我今天输掉了几百镑。由于只有一个投注记录存在,所以最后一个投注必须更新Betfair ID,所以我没有找到有关重复的线索,直到我查看了Betfairs自己的页面。

我确实已经在试图放置它之前检查赌注是否已经被放置,但是在“placebet”方法在同一个赌注记录上同时运行的情况下,这是不好的。

任何帮助非常感谢。

感谢

+1

依靠数据库和网络锁定的问题是什么?锁定记录的最佳方式是锁定记录。 – Paparazzi

回答

1

不,最好的解决方案是将锁保持在数据库中。该应用程序应尽可能无状态。你已经有一个很好的解决方案。

锁定在你的应用程序内容易出错,错误是灾难性的(死锁,应用程序停止工作,直到手动重新启动)。使用数据库锁定更容易,并且错误是可恢复的。

只需获得数据库权限锁定即可。问一个新问题,你在哪里发布你正在做什么的细节。我建议你XLOCK任何你正在工作的投注工作。这样他们只能执行一次。使用数据库锁和事务的力量来完成这项工作。这是迄今为止比应用程序级别的线程更容易。

+0

好的,所以你说我在今天放置了一个锁,当新的未决投注被放置时,然后解锁,这样任何其他尝试同时投注的线程都会遇到一个空记录集由于在数据库中设置了锁定标志)是最佳解决方案吗?我想也许使用数据库和网络流量会减慢这一点,但这是我处理以前的线程问题的方式,所以如果它能以这种方式表达你最好的方式? –

+0

是的,这工作正常。你必须承认你会犯错误,有错误,引入种族和僵局。这是我们工作的方式。该数据库通过自动解决死锁并为您提供强大的数据一致性保证来覆盖您的屁股。我建议您稍后进行一些研究(https://www.google.com/webhp?complete=1&hl=zh-CN#complete=1&hl=zh-CN&q=sql+server+queue+table)。这种添加和出队作业的概念称为队列表。 (不要误导使用服务代理...)。 – usr

0

你总是可以尝试实现像Redis的(redis.io)一个数据库,提供内置的POP功能(http://redis.io/commands/lpop)。 Redis拥有C#客户端,对于任何类型的应用程序都非常有用,因为速度至关重要,因为它将整个数据库保留在内存中。它也是单线程的,可以轻松实现多用户类型应用程序的分配器。

我还建议检查http://kkovacs.eu/cassandra-vs-mongodb-vs-couchdb-vs-redis,因为它列出了Redis和其他dbs的优点和缺点。可以帮助你做出未来数据库决策。

0

老问题,我知道,但是我想把它扔给那些绊倒它的人。

C#(大概是VB.NET)为处理线程同步提供了一些不错的选择。您可以使用lock关键字来阻止执行,直到给定锁定可用;或者,如果要指定用于取得锁定的超时(可能立即),则使用Monitor.TryEnter()

对于这些方法中的任何一种,都需要一个用于锁定的对象。几乎任何对象都会做;如果你不同步访问某个对象本身(集合,数据库连接,无论),你甚至可以实例化一次性的object。对于轮询定时器,后者是典型的。

首先,确保你有一个对象用于同步:现在

public class DatabasePollingClass { 
    object PollingTimerLock = new object(); 
    ... 

,如果你想轮询线程阻塞无限期地等待轮到自己,用lock关键字:

public class DatabasePollingClass { 
    object PollingTimerLock = new object(); 
    ... 

    protected void PollingTimerCallback() { 
     lock (PollingTimerLock) { 
      //Useful stuff here 
     } 
    } 
} 

一次只允许一个线程在lock (PollingTimerLock)代码块内。所有其他线程将无限期地等待,然后一旦它们可以获取自己的锁,就可以继续执行。

但是,你可能不希望这种行为。如果您希望后续线程立即中止(或稍后等待),如果另一个轮询线程仍在运行,则在锁定时可以使用Monitor.TryEnter()。这确实需要稍微谨慎,但是:

public class DatabasePollingClass { 
    object PollingTimerLock = new object(); 
    ... 

    protected void PollingTimerCallback() { 
     if (Monitor.TryEnter(PollingTimerLock)) { //Acquires lock on PollingTimerLock object 
      try { 
       //Useful stuff here 
      } finally { 
       //Releases lock. 
       //You MUST do this in a finally block! (See below.) 
       Monitor.Exit(PollingTimerLock); 
      } 
     } else { 
      Console.WriteLine("Warning: Polling timer overlap. Skipping."); 
     } 
    } 
} 

额外小心从不像lock关键字,Monitor.TryEnter()需要你,当你用它完成手动解除锁定的事实造成的。为了确保发生这种情况,您需要将整个关键部分包装在try区块中,并释放finally区块中的锁定。这是为了确保锁定将被释放,即使轮询方法失败或提前返回。如果该方法在未释放锁的情况下返回,则您的程序将被有效挂起,因为没有其他线程能够获取该锁。

另一个不使用锁定机制的选项是配置你的定时器而不需要重复周期,即一次性定时器。在轮询方法结束时,您将丢弃旧的定时器,并设置一个新的定时器(您还需要在finally块内执行此操作,以确保定时器在方法结束时被重置)。如果您想要在先前轮询的结束以来的某个时间间隔轮询数据库,此方法将非常有用。这是一个微妙的区别,但它也解决了并发轮询尝试的问题。

请注意,这是一个真的简单线程并发示例。只要所有的锁定都是在与UI线程分开的线程上发生的(消息泵本身可能成为争用的焦点),并且您只锁定了一个对象,那么您不必过多担心死锁。那些调试真的很不愉快;症状通常是“应用程序停止响应,现在您可以猜测哪些线程正在等待什么”。

相关问题