2012-03-01 20 views
7

Próbuję napisać metodę bezpieczną dla wątków, która może być wywołana tylko raz (na instancję obiektu). Wyjątek powinien zostać zgłoszony, jeśli został wcześniej wywołany.Jak: Napisz metodę bezpieczną dla wątków, która może być wywołana tylko raz?

Mam dwa rozwiązania. Czy obie są poprawne? Jeśli nie, to co jest z nimi nie tak?

  1. Z lock:

    public void Foo() 
    { 
        lock (fooLock) 
        { 
         if (fooCalled) throw new InvalidOperationException(); 
         fooCalled = true; 
        } 
        … 
    } 
    private object fooLock = new object(); 
    private bool fooCalled; 
    
  2. Z Interlocked.CompareExchange:

    public void Foo() 
    { 
        if (Interlocked.CompareExchange(ref fooCalled, 1, 0) == 1) 
         throw new InvalidOperationException(); 
        … 
    } 
    private int fooCalled; 
    

    Jeśli się nie mylę, to rozwiązanie ma tę zaletę, że lock-free (co wydaje się nieistotne w moim przypadku) i że wymaga mniej prywatnych pól .

Jestem również otwarty na uzasadnione opinie, które rozwiązanie powinno być preferowane, oraz na dalsze sugestie, czy istnieje lepszy sposób.

Odpowiedz

5

Twoje rozwiązanie Interlocked.CompareExchange wygląda najlepiej, a (jak powiedział) jest lock-free. Jest również znacznie mniej skomplikowany niż inne rozwiązania. Zamki są dość ciężkie, podczas gdy CompareExchange można skompilować do pojedynczej instrukcji CAS cpu. Mówię, idź z tym.

+0

Z ciekawości: Kiedy mówisz, że jest "mniej skomplikowany", wydajesz się odnosić do wszystkiego, co dzieje się za ciemnymi; jak oceniasz czytelność/łatwość zrozumienia konstrukcji "Interlocked.ExchangeCompare" dla przeciętnego programisty? – stakx

+1

@stakx: po to są komentarze. Kiedy programista napotka coś, czego nie rozumie, powinien sprawdzić, czy go rozumieją. W ten sposób stają się lepszym programistą. – thecoop

+0

@ theheop Nie zgadzam się, tak, to jest najbardziej poprawne rozwiązanie, ale nie jest to proste, musisz wiedzieć o operacjach atomowych itd. To jest jakikolwiek proces inicjalizacji i radziłbym podążać za wzorcami inicjalizacji, które są szeroko stosowane (np. sprawdzona blokada). Również te wzory uniemożliwiają utratę czegoś, co łatwo może się wydarzyć podczas wykonywania wątków. – ntziolis

0

The dwukrotnie sprawdzane blokady tupot jest to, czego szukasz:

To jest to, czego po:

class Foo 
{ 
    private object someLock = new object(); 
    private object someFlag = false; 


    void SomeMethod() 
    { 
    // to prevent locking on subsequent calls   
    if(someFlag) 
     throw new Exception(); 

    // to make sure only one thread can change the contents of someFlag    
    lock(someLock) 
    { 
     if(someFlag) 
     throw new Exception(); 

     someFlag = true;      
    } 

    //execute your code 
    } 
} 

Na ogół, gdy narażone na problemy takie jak spróbować i postępuj dobrze wiedzieć wzorach, jak ten powyżej.
To sprawia, że ​​jest on rozpoznawalny i mniej podatny na błędy, ponieważ mniej prawdopodobne jest pominięcie czegoś podczas podążania za wzorcem, szczególnie w przypadku wątków.
W twoim przypadku pierwszy, jeśli nie ma większego sensu, ale często będziesz chciał wykonać rzeczywistą logikę, a następnie ustawić flagę. Drugi wątek zostanie zablokowany podczas wykonywania twojego (być może dość kosztownego) kodu.

O drugiej próbce:
Tak, jest poprawny, ale nie komplikuj go bardziej niż jest. Powinieneś mieć bardzo dobre powody, aby nie używać prostego blokowania iw tej sytuacji sprawia to, że kod jest bardziej skomplikowany (ponieważ mniej znany jest Interlocked.CompareExchange()) bez osiągnięcia czegokolwiek (jak wskazałeś, że blokowanie jest mniejsze od blokowania, aby ustawić flagę boolowską nie jest tak naprawdę korzyść w tym przypadku).

+0

** 1. ** Wydaje się, że czytanie/pisanie "someFlag" nie jest atomowe. Jesteś pewien, że to prawda? ** 2. ** A co z rozwiązaniem opartym na 'Interlocked.CompareExchange'? – stakx

+0

sry zapomniałem o najważniejszej linii – ntziolis

+0

Hmm ... blokuje taki problem, jeśli masz zamiar rzucić wyjątek? – stakx

-1
Task task = new Task((Action)(() => { Console.WriteLine("Called!"); })); 
    public void Foo() 
    { 
     task.Start(); 
    } 

    public void Bar() 
    { 
     Foo(); 
     Foo();//this line will throws different exceptions depends on 
       //whether task in progress or task has already been completed 
    }  
+1

Przykro mi to mówić, ale to nie odpowiada na pytanie. (Btw., Jestem świadomy zadania Parallel Library, ale nie można go używać wszędzie, np. Gdy dana metoda implementuje metodę interfejsu.) – stakx

+1

poprawiono według twojego przypadku – pamidur

+0

@pamidur próbujesz rozwiązać inny problem – ntziolis

Powiązane problemy