2010-03-28 39 views
8

Tôi đang sử dụng ThreadPool.QueueUserWorkItem để phát một số tệp âm thanh và không treo lên GUI trong khi làm như vậy..NET ThreadPool QueueUserWorkItem Đồng bộ hóa

Nó hoạt động nhưng có tác dụng phụ không mong muốn.

Trong khi QueueUserWorkItem CallBack Proc đang được thực thi, không có gì ngăn nó bắt đầu chuỗi mới. Điều này làm cho các mẫu trong các chủ đề chồng lên nhau.

Tôi làm cách nào để nó đợi chuỗi đang chạy để kết thúc chạy và chỉ sau đó chạy yêu cầu tiếp theo?

EDIT: private object sync = new Object(); lock (sync) { .......do sound here }

công trình này. chơi theo âm thanh theo thứ tự.

nhưng một số mẫu đang được phát nhiều lần khi tôi tiếp tục gửi yêu cầu âm thanh trước khi yêu cầu phát được kết thúc. sẽ điều tra.

CHỈNH SỬA: là kết quả bên trên của khóa Convoy @Aaronaught được đề cập?

+0

có thể trùng lặp ?: http://stackoverflow.com/questions/1902384/make-a-backgroundworker-do-several-operations-sequentially-without-freezing-the-f/1907501#1907501 – Oliver

Trả lời

7

Đây là vấn đề đồng bộ hóa chuỗi cổ điển, nơi bạn có nhiều khách hàng muốn sử dụng cùng một tài nguyên và cần kiểm soát cách họ truy cập tài nguyên.Trong trường hợp đặc biệt này, hệ thống âm thanh sẵn sàng chơi nhiều hơn một âm thanh cùng một lúc (và điều này thường được mong muốn), nhưng vì bạn không muốn hành vi đó, bạn có thể sử dụng khóa tiêu chuẩn để truy cập cổng vào hệ thống âm thanh :

public static class SequentialSoundPlayer 
{ 
    private static Object _soundLock = new object(); 

    public static void PlaySound(Sound sound) 
    { 
     ThreadPool.QueueUserWorkItem(AsyncPlaySound, sound); 
    } 

    private static void AsyncPlaySound(Object state) 
    { 
     lock (_soundLock) 
     { 
      Sound sound = (Sound) state; 
      //Execute your sound playing here... 
     } 
    } 
} 

trong đó âm thanh là bất kỳ đối tượng nào bạn đang sử dụng để thể hiện âm thanh được phát. Cơ chế này là 'đầu tiên đến, được phục vụ trước' khi nhiều âm thanh vie cho thời gian chơi.


Như đã đề cập trong một câu trả lời khác, hãy cẩn thận với quá nhiều 'chồng lên' âm thanh, khi bạn bắt đầu kết nối ThreadPool.

+0

bằng cách đặt các âm thanh cần được phát trong một AsyncProc duy nhất và sử dụng Khóa (..) sự cố của tôi đã được giải quyết. – iTEgg

6

Bạn có thể sử dụng một chuỗi đơn lẻ có hàng đợi để phát tất cả âm thanh.

Khi bạn muốn phát âm thanh, hãy chèn yêu cầu vào hàng đợi và báo hiệu cho chuỗi đang phát có tệp âm thanh mới được phát. Chuỗi phát âm thanh sẽ nhìn thấy yêu cầu mới và phát yêu cầu đó. Khi âm thanh hoàn thành, nó sẽ kiểm tra xem có thêm âm thanh nào trong hàng đợi không và nếu như vậy phát âm thanh tiếp theo, nếu không nó sẽ chờ yêu cầu tiếp theo. Một trong những vấn đề có thể xảy ra với phương pháp này là nếu bạn có quá nhiều âm thanh cần được phát, bạn có thể nhận được nhật ký tồn đọng để âm thanh có thể đến vài giây hoặc thậm chí có thể trễ tới vài phút. Để tránh điều này, bạn có thể muốn đặt giới hạn về kích thước hàng đợi và thả một số âm thanh nếu bạn có quá nhiều.

+4

+1. Nói cách khác, không sử dụng ThreadPool. Làm luồng cho mình. ThreadPool của thiết kế sử dụng nhiều chủ đề để hoàn thành tất cả các mục công việc xếp hàng nhiều hơn trong thời gian ít hơn. –

+0

Xem thêm http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!1780.thử cho chiến lược F #, nếu đó là con hẻm của bạn. – Brian

+0

Thực ra bạn CÓ THỂ sử dụng một threadpool - có một hàng đợi riêng biệt, khi một mục đi vào và không có quá trình chờ đợi, QUEUES A ITEM WORK. Mục công việc có một cuộc gọi lại sẽ xử lý tất cả các mục chờ. Mỗi hàng đợi sẽ chỉ xếp hàng nếu không có gì xếp hàng đợi. Tôi sử dụng nó rất nhiều trong một ứng dụng giao dịch thời gian thực và nó hoạt động độc đáo - và ngăn tôi quản lý các luồng của mình theo cách thủ công. Nhưng đổ tất cả các yêu cầu vào TreadPool là xấu;) – TomTom

0

Theo sửa đổi của bạn, tạo ra chủ đề của bạn như thế này:

MySounds sounds = new MySounds(...); 
Thread th = new Thread(this.threadMethod, sounds); 
th.Start(); 

Và đây sẽ là điểm vào chủ đề của bạn.

private void threadMethod (object obj) 
{ 
    MySounds sounds = obj as MySounds; 
    if (sounds == null) { /* do something */ } 

    /* play your sounds */ 
} 
+0

Chủ đề t = new Thread (this.FireAttackProc, fireResult); đưa ra lỗi. không thể chuyển đổi từ 'NietzscheBattleships.FireResult' sang 'int' – iTEgg

2

Mã đơn giản nhất bạn có thể viết sẽ như sau:

private object playSoundSync = new object(); 
public void PlaySound(Sound someSound) 
{ 
    ThreadPool.QueueUserWorkItem(new WaitCallback(delegate 
    { 
     lock (this.playSoundSync) 
     { 
     PlaySound(someSound); 
     } 
    })); 
} 

allthough rất đơn giản, nó pontentially có thể mang lại vấn đề:

  1. Nếu bạn chơi rất nhiều (dài hơn) âm thanh đồng thời sẽ có rất nhiều ổ khóa và rất nhiều luồng threadpool được sử dụng hết.
  2. Thứ tự bạn đặt âm thanh không phải là theo thứ tự mà chúng sẽ được phát lại.

trong thực tế, các vấn đề này chỉ nên liên quan nếu bạn phát nhiều âm thanh thường xuyên hoặc nếu âm thanh quá dài.

+0

âm thanh khóa đầy hứa hẹn. có cách nào để sử dụng nó sau đây không? ThreadPool.QueueUserWorkItem (mới WaitCallback (FireResultProc), fireResult); – iTEgg

+0

Điều này hoạt động, nhưng không đảm bảo rằng âm thanh phát theo đúng thứ tự, có thể quan trọng – dan

+1

Nó không chỉ đảm bảo rằng âm thanh phát theo thứ tự đúng, nhưng nếu nhiều âm thanh được phát liên tiếp nhanh chóng sẽ tạo ra khóa lớn ganh đua - nó thậm chí có thể làm cạn kiệt hồ bơi luồng nếu âm thanh đủ dài. – Aaronaught

0

Việc sử dụng ThreadPool không phải là lỗi. Lỗi đang xếp hàng mọi âm thanh như mục công việc. Đương nhiên các hồ bơi thread sẽ bắt đầu đề nhiều hơn nữa. Đây là những gì nó được cho là phải làm.

Tạo hàng đợi của riêng bạn. Tôi có một (AsyncActionQueue). Nó xếp hàng các mục và khi nó có một mục nó sẽ bắt đầu một ThreadPool WorkItem - không phải một cho mỗi mục, ONE (trừ khi một đã được xếp hàng đợi và chưa hoàn thành). Các cuộc gọi lại về cơ bản unqeueues mục và xử lý chúng.

Điều này cho phép tôi có X hàng đợi chia sẻ chuỗi Y (ví dụ: không phải là chủ đề lãng phí) và vẫn nhận được các hoạt động không đồng bộ rất tốt. Tôi sử dụng nó cho một ứng dụng giao dịch UI comples - Cửa sổ X (6, 8) giao tiếp với một cụm dịch vụ trung tâm (nghĩa là một số dịch vụ) và tất cả đều sử dụng hàng đợi không đồng bộ để di chuyển các mục qua lại (tốt, chủ yếu là hướng tới giao diện người dùng)).

Một điều bạn CẦN phải biết - và điều đó đã được nói - là nếu bạn quá tải hàng đợi của bạn, nó sẽ giảm trở lại. Việc cần làm sau đó tùy thuộc vào bạn. Tôi có một tin nhắn ping/pong mà được xếp hàng thường xuyên đến một dịch vụ (từ cửa sổ) và nếu không trả lại trong thời gian, cửa sổ đi màu xám đánh dấu "Tôi là cũ" cho đến khi nó bắt kịp.

2

Một nhà sản xuất/hàng tiêu dùng rất đơn giản sẽ là lý tưởng ở đây - vì bạn chỉ có 1 nhà sản xuất và 1 người tiêu dùng, bạn có thể làm điều đó với khóa tối thiểu.

Đừng sử dụng một phần quan trọng (lock tuyên bố) xung quanh thực tế Play phương pháp/hoạt động như một số người đang gợi ý, bạn có thể dễ dàng kết thúc với một lock convoy. Bạn cần phải khóa, nhưng bạn chỉ nên làm điều đó trong một khoảng thời gian rất ngắn, không phải trong khi âm thanh thực sự đang chơi, đó là một cõi đời đời trong thời gian máy tính.

Something như thế này:

public class SoundPlayer : IDisposable 
{ 
    private int maxSize; 
    private Queue<Sound> sounds = new Queue<Sound>(maxSize); 
    private object sync = new Object(); 
    private Thread playThread; 
    private bool isTerminated; 

    public SoundPlayer(int maxSize) 
    { 
     if (maxSize < 1) 
      throw new ArgumentOutOfRangeException("maxSize", maxSize, 
       "Value must be > 1."); 
     this.maxSize = maxSize; 
     this.sounds = new Queue<Sound>(); 
     this.playThread = new Thread(new ThreadStart(ThreadPlay)); 
     this.playThread.Start(); 
    } 

    public void Dispose() 
    { 
     isTerminated = true; 
     lock (sync) 
     { 
      Monitor.PulseAll(sync); 
     } 
     playThread.Join(); 
    } 

    public void Play(Sound sound) 
    { 
     lock (sync) 
     { 
      if (sounds.Count == maxSize) 
      { 
       return; // Or throw exception, or block 
      } 
      sounds.Enqueue(sound); 
      Monitor.PulseAll(sync); 
     } 
    } 

    private void PlayInternal(Sound sound) 
    { 
     // Actually play the sound here 
    } 

    private void ThreadPlay() 
    { 
     while (true) 
     { 
      lock (sync) 
      { 
       while (!isTerminated && (sounds.Count == 0)) 
        Monitor.Wait(sync); 
       if (isTerminated) 
       { 
        return; 
       } 
       Sound sound = sounds.Dequeue(); 
       Play(sound); 
      } 
     } 
    } 
} 

này sẽ cho phép bạn tăng tốc số âm thanh được chơi bằng cách thiết lập maxSize một số giới hạn hợp lý, giống như 5, sau đó trỏ nó chỉ đơn giản là sẽ loại bỏ các yêu cầu mới. Lý do tôi sử dụng một Thread thay vì ThreadPool chỉ đơn giản là để duy trì một tham chiếu đến các sợi được quản lý và có thể cung cấp dọn dẹp thích hợp.

Điều này chỉ sử dụng một chuỗi và một khóa, vì vậy bạn sẽ không bao giờ có đoàn xe kéo khóa và sẽ không bao giờ phát âm thanh cùng một lúc.

Nếu bạn gặp khó khăn khi hiểu điều này hoặc cần thêm chi tiết, hãy xem Threading in C# và đi đến phần "Nhà sản xuất/Hàng tiêu dùng".

1

Một lựa chọn khác, nếu bạn có thể làm cho (lớn) đơn giản hóa giả định rằng bất kỳ nỗ lực để chơi một âm thanh thứ hai trong khi người đầu tiên vẫn còn chơi sẽ chỉ được lờ, là sử dụng một sự kiện duy nhất:

private AutoResetEvent playEvent = new AutoResetEvent(true); 

public void Play(Sound sound) 
{ 
    ThreadPool.QueueUserWorkItem(s => 
    { 
     if (playEvent.WaitOne(0)) 
     { 
      // Play the sound here 
      playEvent.Set(); 
     } 
    }); 
} 

Điều này dễ chết, với những bất lợi rõ ràng rằng nó sẽ chỉ đơn giản là loại bỏ "thêm" âm thanh thay vì xếp hàng chúng. Nhưng trong trường hợp này, nó có thể là chính xác những gì bạn muốn, và chúng tôi nhận được để sử dụng hồ bơi thread bởi vì chức năng này sẽ trở lại ngay lập tức nếu một âm thanh đã được chơi. Về cơ bản nó là "không có khóa".

0

Thư viện dữ liệu TPL mới của Microsoft có thể là một giải pháp tốt cho loại điều này. Kiểm tra video ở đây - ví dụ mã đầu tiên đã chứng minh phù hợp với yêu cầu của bạn khá chính xác.

http://channel9.msdn.com/posts/TPL-Dataflow-Tour

Các vấn đề liên quan