2012-12-16 14 views
18

Napisałem kontroler i działanie, którego używam jako usługi. Usługa ta jest dość kosztowna. Chciałbym ograniczyć dostęp do tej akcji, jeśli jest już uruchomiona akcja.jak zablokować akcję asp.net mvc?

Czy są jakieś wbudowane w sposób blokowania działania mvc asp.net?

Dzięki

+0

Czy chcesz ograniczyć to na użytkownika, na sesję, na aplikację? – Oshry

+1

Szukasz kolejek. –

+0

ogólne ograniczenie, nie pozwalaj nikomu wejść, jeśli został nazwany – vondip

Odpowiedz

38

Szukasz czegoś takiego?

public MyController : Controller 
{ 
    private static object Lock = new object(); 

    public ActionResult MyAction() 
    { 
     lock (Lock) 
     { 
      // do your costly action here 
     }  
    } 
} 

Powyższy zapobiega innych nici z wykonaniem operacji, jeśli jest gwint przetwarzania kodu wewnątrz bloku lock.

Aktualizacja: tutaj jest, jak to działa

kod metoda jest zawsze wykonywany przez wątku. Na mocno obciążonym serwerze możliwe jest wprowadzanie 2 lub więcej różnych wątków i równoległe uruchamianie metody. Zgodnie z pytaniem, tego właśnie chcesz zapobiec.

Uwaga, jak obiekt private Lock to static. Oznacza to, że jest on współużytkowany we wszystkich instancjach kontrolera. Tak więc, nawet jeśli na stercie są 2 wystąpienia tego kontrolera, oba mają ten sam obiekt Lock. (Obiekt nie musi nawet być nazwany Zablokuj, możesz nazwać go Jerry lub Samantha i nadal będzie służył temu samemu celowi.)

Oto, co się dzieje. Twój procesor może zezwolić tylko na 1 wątek na wpisanie sekcji kodu na raz. W normalnych okolicznościach wątek A mógłby rozpocząć wykonywanie bloku kodu, a następnie wątek B mógł rozpocząć jego wykonywanie. Więc teoretycznie możesz mieć 2 wątki wykonujące tę samą metodę (lub dowolny blok kodu) w tym samym czasie.

Aby temu zapobiec, można użyć słowa kluczowego lock. Gdy wątek wchodzi do bloku kodu owiniętego w sekcji lock, "podnosi" obiekt blokady (co znajduje się w nawiasie po słowie kluczowym lock, a.k.a. Lock, Jerry lub Samantha, które należy oznaczyć jako pole static). Przez czas, w którym wykonywana jest zamknięta sekcja, "zatrzymuje się" na obiekcie blokady. Kiedy wątek wychodzi z zablokowanej sekcji, "rezygnuje" z obiektu blokady. Od momentu, w którym wątek podnosi obiekt blokady, dopóki nie zrezygnuje z obiektu blokady, wszystkie inne wątki nie mogą wejść do zablokowanej sekcji kodu. W efekcie są one "pauzowane", dopóki aktualnie wykonywany wątek nie oddaje obiektu blokady.

Wątek A odbiera obiekt blokady na początku metody MyAction. Zanim zrezygnuje z obiektu blokady, wątek B próbuje również wykonać tę metodę. Jednak nie może odebrać obiektu blokady, ponieważ jest już trzymany przez wątek A. Czeka, aż wątek A zrezygnuje z obiektu blokady.Kiedy to nastąpi, wątek B następnie podnosi obiekt blokady i rozpoczyna wykonywanie bloku kodu. Kiedy zakończy się wykonywanie wątku B, blok porzuca obiekt blokady dla następnego wątku, który jest delegowany do obsługi tej metody.

... ale nie jestem pewien, czy to jest to, czego szukasz ...

Stosując to podejście nie musi uczynić kod działać szybciej. Zapewnia tylko, że blok kodu może być wykonany tylko przez 1 wątek na raz. Zwykle używa się go do celów współbieżności, nie ze względu na wydajność. Jeśli możesz podać więcej informacji na temat konkretnego problemu w pytaniu, może być lepsza odpowiedź niż ta.

Pamiętaj, że kod, który przedstawiłem powyżej spowoduje, że inne wątki będą czekać przed wykonaniem bloku. Jeśli to nie jest to, czego chcesz, i chcesz, aby cała akcja została "pominięta", jeśli jest już wykonywana przez inny wątek, użyj czegoś bardziej podobnego do odpowiedzi Oshry'ego. Możesz przechowywać te informacje w pamięci podręcznej, sesji lub dowolnym innym mechanizmie przechowywania danych.

+0

nie powinien być również blokowany w obiekcie sesji lub pamięci podręcznej? – vondip

+0

Dlaczego uważasz, że musiałaby zostać zachowana do sesji lub pamięci podręcznej? – danludwig

+0

, ponieważ http jest bez sesji, musiałbym zapisać blokadę podczas sesji, prawda? – vondip

2

Można utworzyć atrybut niestandardowy jak [UseLock] jak na swoje wymagania i umieścić go przed swoim działaniu

0

Najprostszym sposobem na to byłoby Zapisz w pamięci podręcznej wartością logiczną wskazującą działanie jest uruchomienie wymaganego BL już:

if (System.Web.HttpContext.Current.Cache["IsProcessRunning"]) 
{ 
    System.Web.HttpContext.Current.Cache["IsProcessRunning"] = true; 
    // run your logic here 
    System.Web.HttpContext.Current.Cache["IsProcessRunning"] = false 
} 

Oczywiście można to zrobić, lub coś podobnego, jak również atrybut.

+3

mógłbyś mieć dwa wątki łączące się w warunek if. to nie jest wystarczająco dobre Obawiam się, – vondip

+0

Jak można uzyskać dwa wątków łączących się w warunek if? Jest to praktycznie niemożliwe, ale jeśli musisz być absolutnie pewien, możesz zablokować przypisanie do pamięci podręcznej. – Oshry

+6

To jest przepis na bardzo brudny błąd w wyścigu. Jeśli coś złego się stanie, jeśli dwa wątki wykonają to samo, użyj blokady. – Vincent

5

Po przeczytaniu i zgodził się z powyższym odpowiedź chciałem nieco inne rozwiązanie: Jeśli chcesz wykryć drugie wezwanie do działania, należy Monitor.TryEnter:

if (!Monitor.TryEnter(Lock, new TimeSpan(0))) 
{ 
    throw new ServiceBusyException("Locked!"); 
} 
try 
{ 
... 
} 
finally { 
    Monitor.Exit(Lock); 
} 

używać tego samego statycznego obiektu blokady jak wyszczególnił @danludwig

0

Mam sugestie na ten temat.

1- https://github.com/madelson/DistributedLock ogólnosystemową roztwór zabezpieczający

2- Hangfire BackgroundJob.Enqueue z [DisableConcurrentExecution (1000)] atrybutu.

Dwa rozwiązania oczekują na zakończenie procesu. Nie chcę zgłaszać błędów na żądanie w tym samym czasie.