2017-06-21 8 views
3

私はC#と.Net Framework 4.7でASP.NET Web APIアプリケーションを開発しています。ロックWeb APIコントローラメソッド

私は一度に1つのスレッドだけが実行したいコントローラのメソッドを持っています。言い換えれば、誰かがこのメソッドを呼び出した場合、別の呼び出しはメソッドが終了するまで待たなければなりません。

私は仕事をすることができるSO answerを見つけました。しかし、ここではキューを使用しており、そのキューを消費する方法はわかりません。その答えでは、キューを消費するWindowsサービスを作成することができますが、自分のソリューションに別のアプリケーションを追加したくないと説明しています。

私はこのようなWeb APIメソッドの内部でロックを追加するために考えた:

[HttpPut] 
[Route("api/Public/SendCommissioning/{serial}/{withChildren}")] 
public HttpResponseMessage SendCommissioning(string serial, bool withChildren) 
{ 
    lock 
    { 
     string errorMsg = "Cannot set commissioning."; 

     HttpResponseMessage response = null; 
     bool serverFound = true; 

     try 
     { 
      [ ... ] 
     } 
     catch (Exception ex) 
     { 
      _log.Error(ex.Message); 

      response = Request.CreateResponse(HttpStatusCode.InternalServerError); 
      response.ReasonPhrase = errorMsg; 
     } 

     return response; 
    } 
} 

しかし、問題がある場合、それは保留中のコールの多くをブロックすることができるので、私は、これは良い解決策だとは思いませんメソッドを実行すると私はすべての保留中の呼び出しを失うか、多分私は間違っていて、コール(スレッド)は他の人が終了するまで待つでしょう。言い換えれば、私はこれを使うとデッドロックになるかもしれないと思う。

私は受け取ったのと同じ順序で呼び出しを実行する必要があるので、これを試しています。このアクションのログを見てください:スレッド[12], [13] and [5]

2017-06-20 09:17:43,306 DEBUG [12] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38441110778119919475, withChildren : False 
2017-06-20 09:17:43,494 DEBUG [13] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38561140779115949572, withChildren : False 
2017-06-20 09:17:43,683 DEBUG [5] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38551180775118959070, withChildren : False 
2017-06-20 09:17:43,700 DEBUG [12] WebsiteAction - EXITING PublicController::SendCommissioning 
2017-06-20 09:17:43,722 DEBUG [5] WebsiteAction - EXITING PublicController::SendCommissioning 
2017-06-20 09:17:43,741 DEBUG [13] WebsiteAction - EXITING PublicController::SendCommissioning 

は、私はそれらのいずれかの終了前に3つの呼び出しを受けます。しかし最後のものは2番目のものの前に終わります[12], [5] and [13]

私はこれを許さないための仕組みが必要です。

私が電話をかけた順番どおりに処理するにはどうすればよいですか?

+0

「これは、保留中の通話をたくさんブロックする可能性があるため、これは良い解決策ではないと思いますか?」それはまさにあなたが望むものではありませんか?彼らは要求された順序で一度に1つずつ処理し、完了したときにのみ戻ってくるはずです。 – FrankerZ

+0

@FrankerZ私はこの説明で質問を更新しました:* 'しかし、これは良い解決策ではないと思います。メソッドを実行する際に問題があると保留中の呼び出しをブロックする可能性があり、保留中の呼び出しをすべて失います多分私は間違っていて、コール(スレッド)は他の人が終了するまで待つでしょう。言い換えれば、私がこれを使用すれば、私はデッドロックになる可能性があると思う」* – VansFannel

答えて

2

ロックソリューションは正常に動作するはずです。要求が失敗すると、ロックが解放され、他の保留中の要求がロックに入ることができます。デッドロックは発生しません。

この解決策の唯一の問題は、Web要求が(クライアント側からのタイムアウトになる可能性がある)長期間にわたってハングしてしまうことです。ハングリクエストの問題を解決するために

public class MyApi : ApiController 
{ 
    public static readonly object LockObject = new object(); 

    [HttpPut] 
    [Route("api/Public/SendCommissioning/{serial}/{withChildren}")] 
    public HttpResponseMessage SendCommissioning(string serial, bool withChildren) 
    { 
     lock (LockObject) 
     { 
      //Do stuff 
     } 
    } 
} 

、あなたはキューを利用し、バックエンドをポーリング(またはあなたが空想している場合、SignalRを試してみてください)あなたの仕事が完了するまで必要があります。例:

//This is a sample with Request/Result classes (Simply implement as you see fit) 
public static class MyBackgroundWorker 
{ 
    private static ConcurrentQueue<KeyValuePair<Guid, Request>> _queue = new ConcurrentQueue<KeyValuePair<Guid, Result>>() 
    public static ConcurrentDictionary<Guid, Result> Results = new ConcurrentDictionary<Guid, Result>(); 

    static MyBackgroundWorker() 
    { 
     var thread = new Thread(ProcessQueue); 
     thread.Start(); 
    } 

    private static void ProcessQueue() 
    { 
     KeyValuePair<Guid, Request> req; 
     while(_queue.TryDequeue(out req)) 
     { 
      //Do processing here (Make sure to do it in a try/catch block) 
      Results.TryAdd(req.Key, result); 
     } 
    } 

    public static Guid AddItem(Request req) 
    { 
     var guid = new Guid(); 
     _queue.Enqueue(new KeyValuePair(guid, req)); 
     return guid; 
    } 
} 


public class MyApi : ApiController 
{ 
    [HttpPut] 
    [Route("api/Public/SendCommissioning/{serial}/{withChildren}")] 
    public HttpResponseMessage SendCommissioning(string serial, bool withChildren) 
    { 
     var guid = MyBackgroundWorker.AddItem(new Request(serial, withChildren)); 
     return guid; 
    } 

    [HttpGet] 
    [Route("api/Public/GetCommissioning/{guid}")] 
    public HttpResponseMessage GetCommissioning(string guid) 
    { 
     if (MyBackgroundWorker.Results.TryRemove(new Guid(guid), out Result res)) 
     { 
      return res; 
     } 
     else 
     { 
      //Return result not done 
     } 
    } 
} 
0

私はあなたのアプローチが1であることを異なるレベルでロックすることができますね。

私は外部サービスとしてredisを利用するシステム(またはWebアプリケーション)に出会った。赤字では、リクエストのキーを保存します。あなたのケースでは、メソッドの名前である可能性があります。この方法では、最初に、ロックが存在するかどうかをチェックして(redisと話す)アクションフィルタを用意し、要求をブロックします。

レディスの良い点は、非常に高速で、キーが消えてしまうタイムアウトを指定しましょう。これにより、ロックが永久に詰まるのを防ぎます。

関連する問題