câu lệnh 'using' vs 'try last'


81

Tôi có một loạt thuộc tính mà tôi sẽ sử dụng khóa đọc / ghi. Tôi có thể triển khai chúng bằng một try finallyhoặc một usingmệnh đề.

Trong try finallytôi sẽ có được khóa trước tryvà phát hành trong finally. Trong usingmệnh đề này, tôi sẽ tạo một lớp có được khóa trong phương thức khởi tạo của nó và giải phóng trong phương thức Dispose của nó.

Tôi đang sử dụng khóa đọc / ghi ở nhiều nơi, vì vậy tôi đang tìm kiếm những cách có thể ngắn gọn hơn try finally. Tôi muốn nghe một số ý kiến ​​về lý do tại sao một cách có thể không được đề xuất hoặc tại sao cách này có thể tốt hơn cách khác.

Phương pháp 1 ( try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set
    {
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

Phương pháp 2:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}

public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}

1
Giống như đã nêu trong nhiều câu trả lời, Phương pháp 2 rất tốt, nhưng để tránh rác trên đống mỗi khi bạn sử dụng khóa, bạn nên thay đổi ReadLock và WriteLock thành cấu trúc. Mặc dù câu lệnh using sử dụng giao diện IDisposable của một cấu trúc, nhưng C # vẫn đủ thông minh để tránh đấm bốc!
Michael Gehling

Câu trả lời:


94

Từ MSDN, sử dụng Statement (C # Reference)

Câu lệnh using đảm bảo rằng Dispose được gọi ngay cả khi một ngoại lệ xảy ra trong khi bạn đang gọi các phương thức trên đối tượng. Bạn có thể đạt được kết quả tương tự bằng cách đặt đối tượng bên trong khối try và sau đó gọi Dispose trong khối cuối cùng; trên thực tế, đây là cách trình biên dịch dịch câu lệnh using. Ví dụ mã trước đó mở rộng thành mã sau tại thời điểm biên dịch (lưu ý thêm dấu ngoặc nhọn để tạo phạm vi giới hạn cho đối tượng):

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

Vì vậy, về cơ bản, nó là cùng một mã nhưng với tính năng kiểm tra null tự động đẹp mắt và một phạm vi bổ sung cho biến của bạn . Tài liệu cũng nói rằng nó "đảm bảo việc sử dụng đúng đối tượng IDisposable", vì vậy bạn cũng có thể nhận được hỗ trợ khung tốt hơn cho bất kỳ trường hợp khó hiểu nào trong tương lai.

Vì vậy, hãy đi với tùy chọn 2.

Có biến bên trong một phạm vi kết thúc ngay sau khi nó không còn cần thiết nữa cũng là một điểm cộng.


điều gì là tốt nhất để giải phóng một tài nguyên không thể được khởi tạo trong câu lệnh using hoặc đang được sử dụng lại hoặc được chuyển ra dưới dạng tham số? try / catch !!!
bjan

2
@bjan tốt, tại sao bạn thậm chí còn xem xét usingtrong trường hợp đó? đó không phải usinglà những gì dành cho.
chakrit

1
đó là lý do tại sao tôi cũng đề cập đến try/catchvì nó có vẻ là cách duy nhất để xử lý thông qua try/catch/finallykhối. hy vọng usingcũng có thể xử lý điều này
bjan

Vâng, try/finallytôi đoán là lựa chọn duy nhất của bạn trong trường hợp đó. IMO, tuy nhiên, tôi nghĩ luôn phải có một số đối tượng / đoạn mã chịu trách nhiệm duy trì thời gian tồn tại của đối tượng trong những trường hợp như vậy (trong đó Dispose () luôn được gọi.) Nếu một lớp chỉ xử lý phần khởi tạo và ai đó khác phải nhớ để xử lý nó, tôi nghĩ rằng nó có loại mùi một chút ở đó. Không chắc làm thế nào một người sẽ thêm điều đó ở cấp độ ngôn ngữ.
chakrit

Tôi nghĩ lý do duy nhất để tránh "sử dụng" là tránh hoàn toàn dùng một lần vì đó là một đối tượng khác mà tôi đoán?
Demetris Leptos,

12

Tôi chắc chắn thích phương pháp thứ hai hơn. Nó ngắn gọn hơn ở điểm sử dụng và ít mắc lỗi hơn.

Trong trường hợp đầu tiên, ai đó đang chỉnh sửa mã phải cẩn thận không chèn bất cứ thứ gì vào giữa lệnh gọi Khóa (Đọc | Viết) Nhận và thử.

(Mặc dù vậy, việc sử dụng khóa đọc / ghi trên các thuộc tính riêng lẻ như thế này thường quá mức cần thiết. Chúng được áp dụng tốt nhất ở cấp độ cao hơn nhiều. Một khóa đơn giản thường đủ ở đây vì khả năng tranh chấp có lẽ là rất nhỏ với thời gian khóa được giữ và có được một khóa đọc / ghi là một hoạt động tốn kém hơn một khóa đơn giản).


Làm thế nào về việc không an toàn? Tôi biết một lần thử cuối cùng sẽ luôn kích hoạt khối cuối cùng, có cách nào để xử lý không được gọi không?
Jeremy

1
Không, câu lệnh using về cơ bản là đường cú pháp cho mẫu thử / cuối cùng.
Blair Conrad

Mô hình sử dụng đảm bảo rằng đối tượng sẽ luôn được xử lý.
Nathen Silver

Nếu bạn sử dụng bộ phản xạ, bạn sẽ thấy rằng khối đang sử dụng được trình biên dịch dịch thành một cấu trúc thử ... cuối cùng, vì vậy ở mức IL chúng tương đương nhau. 'Using' chỉ là đường tổng hợp.
Rob Walker

^^ hãy nhớ rằng có những kịch bản tiềm năng mà cuối cùng không được gọi. Ví dụ như cắt điện. ;)
Quibblesome

8

Xem xét khả năng cả hai giải pháp đều không tốt vì chúng che giấu các ngoại lệ .

A trymà không có a catchrõ ràng là một ý tưởng tồi; xem MSDN để biết lý do tại sao usingcâu lệnh này cũng nguy hiểm.

Cũng lưu ý rằng Microsoft hiện khuyến nghị ReaderWriterLockSlim thay vì ReaderWriterLock.

Cuối cùng, lưu ý rằng các ví dụ của Microsoft sử dụng hai khối try-catch để tránh những vấn đề này, ví dụ:

try
{
    try
    {
         //Reader-writer lock stuff
    }
    finally
    {
         //Release lock
    }
 }
 catch(Exception ex)
 {
    //Do something with exception
 }

Một giải pháp đơn giản, nhất quán, sạch sẽ là một mục tiêu tốt, nhưng giả sử bạn không thể chỉ sử dụng lock(this){return mydateetc;}, bạn có thể xem xét lại cách tiếp cận; với nhiều thông tin hơn, tôi chắc chắn rằng Stack Overflow có thể trợ giúp ;-)


2
Một lần thử cuối cùng không nhất thiết phải che giấu ngoại lệ. Trong ví dụ của tôi, khóa được hỏi, sau đó nếu bất kỳ ngoại lệ nào được ném vào phạm vi, khóa sẽ được giải phóng, nhưng ngoại lệ vẫn sẽ bong bóng.
Jeremy

1
@ Jeremy: nếu khối finally bạn ném một ngoại lệ, nó sẽ che ném ngoại lệ trong khối try của bạn - đó là những gì bài viết MSDN đang nói là (giống) vấn đề với cú pháp sử dụng
Steven A. Lowe

3
Nó sẽ không che dấu ngoại lệ, nó sẽ thay thế ngoại lệ theo cách giống hệt như ý muốn của bạn.
Jon Hanna

5

Cá nhân tôi sử dụng câu lệnh C # "using" thường xuyên nhất có thể, nhưng có một số điều cụ thể mà tôi thực hiện cùng với nó để tránh các vấn đề tiềm ẩn được đề cập. Để minh họa:

void doSomething()
{
    using (CustomResource aResource = new CustomResource())
    {
        using (CustomThingy aThingy = new CustomThingy(aResource))
        {
            doSomething(aThingy);
        }
    }
}

void doSomething(CustomThingy theThingy)
{
    try
    {
        // play with theThingy, which might result in exceptions
    }
    catch (SomeException aException)
    {
        // resolve aException somehow
    }
}

Lưu ý rằng tôi tách câu lệnh "using" thành một phương thức và việc sử dụng (các) đối tượng thành một phương thức khác bằng một khối "try" / "catch". Tôi có thể lồng một số câu lệnh "using" như thế này cho các đối tượng liên quan (đôi khi tôi đi sâu ba hoặc bốn câu lệnh sản xuất của mình).

Trong Dispose()các phương thức của tôi cho các IDisposablelớp tùy chỉnh này , tôi bắt các ngoại lệ (nhưng KHÔNG phải lỗi) và ghi nhật ký chúng (sử dụng Log4net). Tôi chưa bao giờ gặp phải trường hợp nào trong số đó có thể ảnh hưởng đến quá trình xử lý của tôi. Các lỗi tiềm ẩn, như thường lệ, được phép lan truyền lên ngăn xếp cuộc gọi và thường kết thúc xử lý bằng một thông báo thích hợp (lỗi và dấu vết ngăn xếp) được ghi lại.

Nếu bằng cách nào đó tôi gặp phải tình huống trong đó một ngoại lệ quan trọng có thể xảy ra Dispose(), tôi sẽ thiết kế lại cho tình huống đó. Thành thật mà nói, tôi nghi ngờ điều đó sẽ xảy ra.

Trong khi đó, phạm vi và lợi thế dọn dẹp của việc "sử dụng" khiến nó trở thành một trong những tính năng C # yêu thích nhất của tôi. Nhân tiện, tôi làm việc bằng Java, C # và Python như các ngôn ngữ chính của tôi, với rất nhiều ngôn ngữ khác được đưa vào đây đó và ở đó, và "sử dụng" là một trong những tính năng ngôn ngữ yêu thích nhất của tôi vì nó là một công việc thực tế, hàng ngày .


Mẹo, để dễ đọc mã: Căn chỉnh và nén các câu lệnh use bằng cách không có dấu ngoặc nhọn ngoại trừ "using" bên trong.
Bent Tranberg

4

Tôi thích lựa chọn thứ 3

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

public DateTime MyDateTime{
  get{
    lock(_myDateTimeLock){return _myDateTime;}
  }
  set{
    lock(_myDateTimeLock){_myDateTime = value;}
  }
}

Trong số hai tùy chọn của bạn, tùy chọn thứ hai là rõ ràng nhất và dễ hiểu hơn những gì đang xảy ra.


câu lệnh lock không hoạt động giống như cách ReaderWriterLock làm.
chakrit

@chakrit: Không, nhưng trừ khi bạn biết thực sự có tranh chấp về khóa, chúng có thể hoạt động hiệu quả hơn.
Michael Burr

Đúng, nhưng bạn nên luôn sử dụng khóa mặt cười trước. Câu hỏi của subby không nêu bất kỳ điều gì về các vấn đề hiệu suất hoặc các yêu cầu không cho phép khóa. Do đó, không có lý do gì để thử và khéo léo. Chỉ cần khóa nó và lắc nó.

4

"Bó thuộc tính" và khóa ở cấp nhận thuộc tính và cấp thiết lập có vẻ không đúng. Khóa của bạn quá mịn. Trong hầu hết cách sử dụng đối tượng điển hình, bạn muốn đảm bảo rằng bạn đã có được khóa để truy cập nhiều thuộc tính cùng một lúc. Trường hợp cụ thể của bạn có thể khác nhưng tôi hơi nghi ngờ điều đó.

Dù sao, có được khóa khi bạn truy cập đối tượng thay vì thuộc tính sẽ cắt giảm đáng kể số lượng mã khóa mà bạn sẽ phải viết.


Vâng, tôi thực sự thấy rõ quan điểm của bạn. Hầu hết các thuộc tính của tôi thực sự là bools, int, mà tôi không cần phải khóa vì chúng phải là nguyên tử. Có một số ngày giờ, chuỗi mà tôi muốn khóa. vì thiểu số sẽ cần ổ khóa, tôi thight tốt nhất để đặt nó trên tài sản
Jeremy

4

DRY nói: giải pháp thứ hai. Giải pháp đầu tiên sao chép logic của việc sử dụng khóa, trong khi giải pháp thứ hai thì không.


1

Các khối Try / Catch thường dùng để xử lý ngoại lệ, trong khi sử dụng các khối được sử dụng để đảm bảo rằng đối tượng được xử lý.

Đối với khóa đọc / ghi, thử / bắt có thể hữu ích nhất, nhưng bạn cũng có thể sử dụng cả hai, như vậy:

using (obj)
{
  try { }
  catch { }
}

để bạn có thể gọi ngầm giao diện IDisposable của mình cũng như làm cho việc xử lý ngoại lệ trở nên ngắn gọn.


0

Tôi nghĩ rằng phương pháp 2 sẽ tốt hơn.

  • Mã đơn giản hơn và dễ đọc hơn trong thuộc tính của bạn.
  • Ít bị lỗi hơn vì mã khóa không phải viết lại nhiều lần.

0

Mặc dù tôi đồng ý với nhiều ý kiến ​​ở trên, bao gồm cả mức độ chi tiết của khóa và xử lý ngoại lệ đáng ngờ, câu hỏi là một trong những cách tiếp cận. Hãy để tôi cung cấp cho bạn một lý do chính khiến tôi thích sử dụng hơn là thử {} cuối cùng là mô hình ... trừu tượng.

Tôi có một mô hình rất giống với mô hình của bạn với một ngoại lệ. Tôi đã xác định một giao diện cơ sở ILock và trong đó tôi cung cấp một phương thức có tên là Acquire (). Phương thức Acquire () trả về đối tượng IDisposable và kết quả là nếu đối tượng mà tôi đang xử lý thuộc loại ILock thì nó có thể được sử dụng để thực hiện một phạm vi khóa. Tại sao nó lại quan trọng?

Chúng tôi xử lý nhiều cơ chế và hành vi khóa khác nhau. Đối tượng khóa của bạn có thể có thời gian chờ cụ thể được sử dụng. Việc triển khai khóa của bạn có thể là khóa màn hình, khóa đầu đọc, khóa ghi hoặc khóa xoay. Tuy nhiên, từ quan điểm của người gọi, tất cả những điều đó đều không liên quan, điều họ quan tâm là hợp đồng khóa tài nguyên có được tôn trọng hay không và khóa thực hiện điều đó theo cách phù hợp với việc thực hiện nó.

interface ILock {
    IDisposable Acquire();
}

class MonitorLock : ILock {
    IDisposable Acquire() { ... acquire the lock for real ... }
}

Tôi thích mô hình của bạn, nhưng tôi sẽ cân nhắc việc ẩn cơ chế khóa khỏi người gọi. FWIW, tôi đã đo chi phí của kỹ thuật sử dụng so với thử nghiệm cuối cùng và chi phí phân bổ đối tượng dùng một lần sẽ có chi phí hiệu suất từ ​​2-3%.


0

Tôi ngạc nhiên là không ai đề xuất đóng gói thử cuối cùng trong các hàm ẩn danh. Cũng giống như kỹ thuật khởi tạo và hủy bỏ các lớp với câu lệnh using, điều này giữ khóa ở một chỗ. Bản thân tôi chỉ thích điều này bởi vì tôi muốn đọc từ "cuối cùng" hơn là từ "Vứt bỏ" khi tôi nghĩ về việc mở khóa.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                rwlMyLock_m,
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                rwlMyLock_m,
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private static DateTime ReadLockedMethod(
        ReaderWriterLock rwl,
        ReadLockMethod method
    )
    {
        rwl.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwl.ReleaseReaderLock();
        }
    }

    private static void WriteLockedMethod(
        ReaderWriterLock rwl,
        WriteLockMethod method
    )
    {
        rwl.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwl.ReleaseWriterLock();
        }
    }
}

0

SoftwareJedi, tôi không có tài khoản nên không thể chỉnh sửa câu trả lời của mình.

Trong mọi trường hợp, phiên bản trước không thực sự tốt cho mục đích sử dụng chung vì khóa đọc luôn yêu cầu giá trị trả về. Điều này khắc phục điều đó:

class StackOTest
{
    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            DateTime retval = default(DateTime);
            ReadLockedMethod(
                delegate () { retval = dtMyDateTime_m; }
            );
            return retval;
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private void ReadLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(Action method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}

0

Phần sau tạo các phương thức mở rộng cho lớp ReaderWriterLockSlim cho phép bạn thực hiện những việc sau:

var rwlock = new ReaderWriterLockSlim();
using (var l = rwlock.ReadLock())
{
     // read data
}
using (var l = rwlock.WriteLock())
{
    // write data
}

Đây là mã:

static class ReaderWriterLockExtensions() {
    /// <summary>
    /// Allows you to enter and exit a read lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitReadLock on dispose</returns>
    public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim)
    {
        // Enter the read lock
        readerWriterLockSlim.EnterReadLock();
        // Setup the ExitReadLock to be called at the end of the using block
        return new OnDispose(() => readerWriterLockSlim.ExitReadLock());
    }
    /// <summary>
    /// Allows you to enter and exit a write lock with a using statement
    /// </summary>
    /// <param name="readerWriterLockSlim">The lock</param>
    /// <returns>A new object that will ExitWriteLock on dispose</returns>
    public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock)
    {
        // Enter the write lock
        rwlock.EnterWriteLock();
        // Setup the ExitWriteLock to be called at the end of the using block
        return new OnDispose(() => rwlock.ExitWriteLock());
    }
}

/// <summary>
/// Calls the finished action on dispose.  For use with a using statement.
/// </summary>
public class OnDispose : IDisposable
{
    Action _finished;

    public OnDispose(Action finished) 
    {
        _finished = finished;
    }

    public void Dispose()
    {
        _finished();
    }
}

0

Trên thực tế, trong ví dụ đầu tiên của bạn, để làm cho các giải pháp có thể so sánh được, bạn cũng sẽ triển khai IDisposableở đó. Sau đó, bạn sẽ gọi Dispose()từ finallykhối thay vì mở khóa trực tiếp.

Sau đó, bạn sẽ thực hiện "táo với táo" (và MSIL) theo chiều ngược lại (MSIL sẽ giống nhau cho cả hai giải pháp). Có lẽ vẫn nên sử dụng usingvì phạm vi được bổ sung và vì Khung sẽ đảm bảo việc sử dụng thích hợp IDisposable(cái sau ít có lợi hơn nếu bạn đang IDisposabletự triển khai ).


-1

Tôi thật ngốc. Có một cách để làm cho điều đó thậm chí còn đơn giản hơn bằng cách biến các phương thức bị khóa thành một phần của mỗi trường hợp (thay vì tĩnh như trong bài viết trước của tôi). Bây giờ tôi thực sự thích điều này hơn vì không cần phải chuyển `rwlMyLock_m 'sang một số lớp hoặc phương thức khác.

class StackOTest
{
    private delegate DateTime ReadLockMethod();
    private delegate void WriteLockMethod();

    static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
    private DateTime dtMyDateTime_m;
    public DateTime MyDateTime
    {
        get
        {
            return ReadLockedMethod(
                delegate () { return dtMyDateTime_m; }
            );
        }
        set
        {
            WriteLockedMethod(
                delegate () { dtMyDateTime_m = value; }
            );
        }
    }

    private DateTime ReadLockedMethod(ReadLockMethod method)
    {
        rwlMyLock_m.AcquireReaderLock(0);
        try
        {
            return method();
        }
        finally
        {
            rwlMyLock_m.ReleaseReaderLock();
        }
    }

    private void WriteLockedMethod(WriteLockMethod method)
    {
        rwlMyLock_m.AcquireWriterLock(0);
        try
        {
            method();
        }
        finally
        {
            rwlMyLock_m.ReleaseWriterLock();
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.