2013-12-21 77 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線程分開的線程上發生的(消息泵本身可能成爲爭用的焦點),並且您只鎖定了一個對象,那麼您不必過多擔心死鎖。那些調試真的很不愉快;症狀通常是「應用程序停止響應,現在您可以猜測哪些線程正在等待什麼」。

相關問題