[macruby-changes] [3595] MacRuby/trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Feb 23 15:51:06 PST 2010


Revision: 3595
          http://trac.macosforge.org/projects/ruby/changeset/3595
Author:   ernest.prabhakar at gmail.com
Date:     2010-02-23 15:51:06 -0800 (Tue, 23 Feb 2010)
Log Message:
-----------
Redid Dispatch library with Job and Proxy

Modified Paths:
--------------
    MacRuby/trunk/lib/dispatch/README.rdoc
    MacRuby/trunk/lib/dispatch/enumerable.rb
    MacRuby/trunk/lib/dispatch/job.rb
    MacRuby/trunk/lib/dispatch/queue.rb
    MacRuby/trunk/lib/dispatch.rb
    MacRuby/trunk/sample-macruby/Scripts/gcd/dispatch_methods.rb
    MacRuby/trunk/sample-macruby/Scripts/gcd/ring_buffer.rb
    MacRuby/trunk/spec/macruby/library/dispatch/enumerable_spec.rb
    MacRuby/trunk/spec/macruby/library/dispatch/job_spec.rb

Added Paths:
-----------
    MacRuby/trunk/lib/dispatch/proxy.rb
    MacRuby/trunk/spec/macruby/library/dispatch/proxy_spec.rb
    MacRuby/trunk/spec/macruby/library/dispatch/queue_spec.rb

Removed Paths:
-------------
    MacRuby/trunk/lib/dispatch/actor.rb
    MacRuby/trunk/lib/dispatch/dispatch.rb
    MacRuby/trunk/spec/macruby/library/dispatch/actor_spec.rb
    MacRuby/trunk/spec/macruby/library/dispatch/dispatch_spec.rb

Modified: MacRuby/trunk/lib/dispatch/README.rdoc
===================================================================
--- MacRuby/trunk/lib/dispatch/README.rdoc	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/README.rdoc	2010-02-23 23:51:06 UTC (rev 3595)
@@ -30,198 +30,125 @@
 
 We also assume that you are already familiar with Ruby, though not necessarily MacRuby. No prior knowledge of C or GCD is assumed or required, but the {dispatch(3) man page}[http://developer.apple.com/mac/library/DOCUMENTATION/Darwin/Reference/ManPages/man3/dispatch.3.html] may be helpful if you wish to better understand the underlying semantics.
 
-== Dispatch.fork
+== Dispatch::Job: Easy Concurrency
 
-The most basic method of the Dispatch module is +fork+, which allows you to schedule work asynchronously.
+The easiest way to perform concurrent work is via a Dispatch::Job object. Say you have a complex, long-running calculation you want to happen in the background. Create a job by passing the block you want to execute:
 
 	require 'dispatch'
-	Dispatch.fork { p "Do this somewhere else" }
+	job = Dispatch::Job.new { Math.sqrt(10**100) }
 
-This atomically[http://en.wikipedia.org/wiki/Atomic_operation] adds each block to GCD's default concurrent queue, then returns immediately. Concurrent queues schedule as many simultaneous blocks as they can on a first-in/first-out (FIFO[http://en.wikipedia.org/wiki/FIFO]) basis, as long as there are threads available.  If there are spare CPUs, the system will automatically create more threads -- and reclaim them when idle -- allowing GCD to dynamically scale the number of threads based on the overall system load.
+This atomically[http://en.wikipedia.org/wiki/Atomic_operation] adds the block to GCD's default concurrent queue, then returns immediately so you don't stall the main thread.
 
-=== Dispatch::Job
+Concurrent queues schedule as many simultaneous blocks as they can on a first-in/first-out (FIFO[http://en.wikipedia.org/wiki/FIFO]) basis, as long as there are threads available.  If there are spare CPUs, the system will automatically create more threads -- and reclaim them when idle -- allowing GCD to dynamically scale the number of threads based on the overall system load.  Thus -- unlike with threads, which choke when you create too many -- you can generally create as many jobs as you want, and GCD will do the right thing. 
 
-Invoking +fork+ returns a +Job+ object which can be used to track completion of work:
+=== Job#value: Asynchronous Return Values
 
-	job = Dispatch.fork { @sum = 2 + 2 }
-	
-You can pass that job to multiple forks, to track them as a group:
+The downside of asynchrony is that you don't know exactly when your job will execute.  Fortunately, +Dispatch::Job+ duck-types +Thread[http://ruby-doc.org/core/classes/Thread.html]+ as much as possible, so you can call +value[http://ruby-doc.org/core/classes/Thread.html#M000460]+ to obtain the result of executing that block:
 
-	Dispatch.fork(job) { @product = 3 * 3 }
-
-Call +join+ to wait until all the work completes:
-
-	job.join
-	puts @sum + @product # => 10
+	@result = job.value
+	puts @result.to_int.to_s.size # => 51
 	
-Call +value+ to retrieve the return value of block passed in when the job was created:
-	
-	puts job.value # => 4
-	
-This will first call +join+ to ensure the value has been calculated, allowing it to be used as an explicit Future[http://en.wikipedia.org/wiki/Futures_and_promises]. However, this means the current thread may stall for an indefinite amount of time.  Alternatively, both +join+ and +value+ can be called with a block, which will be invoked asynchronously when the job has completed:
+This will wait until the value has been calculated, allowing it to be used as an {explicit Future}[http://en.wikipedia.org/wiki/Futures_and_promises]. However, this may stall the main thread indefinitely, which reduces the benefits of concurrency.  
 
-	job.join { puts @sum + @product }
+Wherever possible, you should instead attempt to figure out exactly _when_  and _why_ you need to know the results of asynchronous work. Then, call +value+ with a block to also perform _that_ work asynchronously once the value has been calculated -- all without blocking the main thread:
 
-	job.value { |v| puts v }
+job.value {|v| p v.to_int.to_s.size } # => 51 (eventually)
 
+=== Job#add: Coordinating Work
 
-=====  Variable Scope
+More commonly, you will have multiple units of work you'd like to perform in parallel.  You can add blocks to an existing job using the +<<+ ('shovel') operator:
 
-The asynchronous blocks used by GCD are (almost) just standard ruby blocks, and thus have access to the local context:
+	job.add { Math.sqrt(2**64) }
 
-	filename = "/etc/passwd"
-	Dispatch.fork { File.open(filename) {|f| puts f} }
+If there are multiple blocks in a job, +value+ will wait until they all finish then return an array of the results:
 
-The one caveat is that since the block may run at a later time, local (dynamic) variables are always copied rather than referenced:
+job.value {|ary| p ary.sort.inspect } # => [4294967296.0, 1.0E50]
 
-	filename = "/etc/passwd"
-	1.times do { filename = "/etc/group" }
-	p filename # => "/etc/group"
-	job = Dispatch.fork { filename = "/etc/shell" }
-	job.join { p filename } # => "/etc/group"
+Note that the returned order is undefined, since asynchronous blocks can complete in any order.
 
-In practice this is not a significant limitation, since it only copies the _variable_ -- not the object itself. Thus, operations that mutate (i.e., modify in place) the underlying object -- unlike those that reassign the variable -- behave as expected:
+== Dispatch::Proxy: Protecting Shared Data
 
-	ary = ["/etc/passwd"]
-	job = Dispatch.fork { ary << "/etc/shell" }
-	job.join { p ary } # => ["/etc/passwd", "/etc/shell"]
+Concurrency would be easy if everything was {embarrassingly parallel}[http://en.wikipedia.org/wiki/Embarrassingly_parallel], but 
+becomes tricky when we need to share data between threads. If two threads try to modify the same object at the same time, it could lead to inconsist (read: _corrupt_) data.  There are well-known techniques for preventing this sort of data corruption (e.g. locks[http://en.wikipedia.org/wiki/Lock_(computer_science)] andmutexes[http://en.wikipedia.org/wiki/Mutual%20eclusion]), but these have their own well-known problems (e.g., deadlock[http://en.wikipedia.org/wiki/Deadlock], and {priority inversion}[http://en.wikipedia.org/wiki/Priority_inversion]).
 
-In this case, the local variable +ary+ still points to the same object that was mutated, so it contains the new value.
+Because Ruby traditionally had a global VM lock (or GIL[http://en.wikipedia.org/wiki/Global_Interpreter_Lock]), only one thread could modify data at a time, so developers never had to worry about these issues; then again, this also meant they didn't get much benefit from additional threads.  
 
-Note however that Ruby treats the accumulation operations ("+=", "||=", etc.) as syntactic sugar over assignment, and thus those operations only affect the copy of the variable:
+In MacRuby, every thread has its own Virtual Machine, which means all of them can access Ruby objects at the same time -- great for concurrency, not so great for data integrity. Fortunately, GCD provides _serial queues_ for {lock-free synchronization}[http://en.wikipedia.org/wiki/Non-blocking_synchronization], by ensuring that only one thread a time accesses a particular object -- without the complexity and inefficiency of locking. Here we will focus on +Dispatch::Proxy+, a high-level construct that implements the {Actor model}[http://en.wikipedia.org/wiki/Actor_model] by wrapping any arbitrary Ruby object with a +SimpleDelegate+ that only allows execution of one method at a time (i.e., serializes data access on to a private queue).
 
-	ary = ["/etc/passwd"]
-	Dispatch.fork { ary += ["/etc/shell"] }
-	job.join { p ary } # => ["/etc/passwd" ]
+=== Job#sync Creating Proxies
 
-When in doubt, simply use instance or global variables (e.g., anything with a sigil[http://en.wikipedia.org/wiki/Sigil_(computer_programming)]), as those have a well-defined existence outside the local scope, and are thus referenced directly by the dispatched block:
+The easiest way to create a Proxy is to first create an empty Job:
 
-	@ary = ["/etc/passwd"]
-	Dispatch.fork { @ary += ["/etc/shell"] }
-	job.join { p @ary } # => ["/etc/passwd", "/etc/shell"]
+	job = Dispatch::Job.new
 
+then ask it to wrap the object you want to modify from multiple threads:
 
-== Synchronization
+	@hash = job.sync {}
+	@hash.class # => Dispatch::Proxy
+	puts @hash.to_s  # => "{}"
+	
+=== method_missing: Using Proxies
 
-Those of you who have done multithreaded programming in other languages may have (rightly!) shuddered at the early mention of mutating an object asynchronously, as that could lead to data corruption when done from multiple threads:
+Now you can use that proxy safely inside Dispatch blocks, just as if it were the delegate object, 
+	
+	[64, 100].each do |n|
+		job << { @hash[n] = Math.sqrt(10**n) }
+	end
+	puts @hash.inspect # => {64 => 1.0E32, 100 => 1.0E50}
 
-	s = "/etc/passwd"
-	# NEVER do this
-	Dispatch.async { s.gsub!("passwd", "shell") }
-	Dispatch.async { s.gsub!("passwd", "group") }
-	s # => who knows? "/etc/shoup"?
+In this case, it will perform the +sqrt+ asynchronously on the concurrent queue. 
 
-Because Ruby 1.8 had a global VM lock (or GIL[http://en.wikipedia.org/wiki/Global_Interpreter_Lock]), you never had to worry about issues like these, as everything was automatically serialized by the interpreter. True, this reduced the benefit of using multiple threads, but it also meant that Ruby developers didn't have to learn about locks[http://en.wikipedia.org/wiki/Lock_(computer_science)], mutexes[http://en.wikipedia.org/wiki/Mutual%20eclusion], deadlock[http://en.wikipedia.org/wiki/Deadlock], and {priority inversion}[http://en.wikipedia.org/wiki/Priority_inversion].
-
-
-=== Dispatch::Queue
-
-Fortunately, even though MacRuby no longer has a global VM lock, you (mostly) still don't need to know about all those things, because GCD provides {lock-free synchronization}[http://en.wikipedia.org/wiki/Non-blocking_synchronization] via serial queues. In contrast to the _concurrent_ queues GCD automatically creates for every process ("global"), the queues you create yourself ("private") are _serial_ -- that is, they only run one block at a time. 
-
-==== Dispatch.queue
-
-Instead of locks, simply create a serial queue for each object you need to protect using the +queue+ method:
-
-	s = "/etc/passwd"
-	q_s = Dispatch.queue(s)
-	# => Dispatch.comparable.nsstring.nsmutablestring.8592077600.1266360156.88218
-	q_s.class # => Dispatch::Queue
+=== __async__: Asynchronous Callbacks
 	
-That long funny-looking string is the queue's +label+, a (relatively) unique combination of the passed object's inheritance chain,  id, and the queue creation time.  It is useful for debugging, as it is displayed in log messages and the {Dispatch  Instrument}[http://developer.apple.com/iPhone/library/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Built-InInstruments/Built-InInstruments.html#//apple_ref/doc/uid/TP40004652-CH6-SW41].
+As with Dispatch::Job, you can make any invocation asynchronous by passing a block:
 
-==== Queue#async
+	@hash.inspect { |s| p s } # => {64 => 1.0E32, 100 => 1.0E50}
 
-To schedule a block on a serial queue, just call +async+::
+If you don't want the +Proxy+ to swallow the block, you can disable it by setting +__async__+ to false:
 
-	q_s.async { s.gsub!("passwd", "shell") }
-	q_s.async { s.gsub!("passwd", "group") }
+	@hash.__async__ = false
 	
-This is analogous as +Dispatch.async(priority)+, which is actually a convenience function wrapping:
-	
-	Dispatch::Queue.concurrent(priority).async
+As elsewhere in Ruby, the "__" namespace private methods, in this case so they don't conflict with delegate methods. 
 
-where +Dispatch::Queue.concurrent(priority)+ returns the global concurrent queue for a given +priority+.
+=== __value__: Returning Delegate
 
-===== Implementation Note
+If for any reason you need to retrieve the delegate object, simply call +__value__+:
 
-For the curious, here's what happens behind the scenes.  When you add a block to an empty serial queue, GCD in turn adds that queue to the default concurrent queue, just as if it were a block. 
-
-==== Queue#sync
-
-But wait, how do we know when that work has been completed? By calling +async+'s synchronous cousin, +sync+:
-
-	q.sync { p s } # => "/etc/shell", since that always runs first
-
-Because serial queues process each block in the order it was received, calling +sync+ after a series of +async+ calls ensures that everything has completed. It is important to only call +sync+ when absolutely necessary, though, as it stops the current thread and thus increases the risk of deadlock, priority inversion, and all the other scary things I said you didn't need to worry about.
-
-==== Group#new
-
-Of course, using a serial queue alone isn't very interesting, as it doesn't provide any concurrency.  The real power comes from using concurrent queues to do the computationally-intensive work, and serial queues to safely save the results to a shared data structure. The easiest way to do this is by creating and passing an explicit +Group+ object, which can be used as a parameter to +Queue#async+:
-
-	@sum = 0
-	q_sum = Dispatch.queue(@sum)
-	g = Dispatch::Group.new
-	Dispatch.async(g) do
-		v1 = Math.sqrt(10**100)
-		q_sum.async(g) { @sum += v1 }
-	end
-	Dispatch.async(g) do
-		v2 = Math.sqrt(10**1000)
-		q_sum.async(g) { @sum += v2 }
-	end
-
-	g.join { p @sum }
-
-The invocations nest, so Group#join won't return until +q_sum.async(g)+ has completed, even if it hadn't yet been submitted.
-
-=== Dispatch::Actor
-
-This pattern of associating a queue with an object is so common and powerful it even has a name: the {Actor model}[http://en.wikipedia.org/wiki/Actor_model].  MacRuby provides a +Dispatch::Actor+ class that uses SimpleDelegator[http://ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html] to guard access to any object, so that all invocations automatically occur on a private serial queue.
+	delegate = @hash.__value__
+	puts delegate.class # => Hash
 	
-==== Dispatch.wrap
+This differs from +SimpleDelegate#__getobj__+ in it will first wait until any pending asynchronous blocks have executed.
 
-The simplest way to turn an object into an actor is with the +wrap+ method:
+====  Caveat: Local Variables
 
-	s = "/etc/passwd"
-	a_s = Dispatch.wrap(s)
+Because Dispatch blocks may execute after the local context has gone away, you should always store Proxy objects in a non-local  variable: instance, class, or global -- anything with a sigil[http://en.wikipedia.org/wiki/Sigil_(computer_programming)]. 
 
-That's it! Apart from a small number of basic methods (e.g., +class+, +object_id+), everything is passed to the delegated object for execution:
+Note that we can as usual _access_ local variables from inside the block; GCD automatically copies them, which is why this works as expected:
 
-	a_s.gsub!("passwd", "shell")
-	p a_s.to_s # => "/etc/shell"
+	n = 42
+	job = Dispatch::Job.new { p n }
+	job.join # => 42
 	
-By default, wrapped methods are invoked synchronously and immediately return their value:
-	
-	a_s.gsub("shell", "group") # => "/etc/group"
+but this doesn't:
 
-But like everything else in GCD, we'd rather you pass it a block so it can execute asynchronously:
+	n = 0
+	job = Dispatch::Job.new { n = 42 }
+	job.join
+	p n # => 0 
 
-	a_s.gsub("group", "passwd") {|v| p v }# => "/etc/passwd"
+The general rule is to *avoid assigning variables inside a Dispatch block*.  Assigning local variables will have no effect (outside that block), and assigning other variables may replace your Proxy object with a non-Proxy version.  Remember also that Ruby treats the accumulation operations ("+=", "||=", etc.) as syntactic sugar over assignment, and thus those operations only affect the copy of the variable:
 
-Voila, make any object thread-safe, asynchronous, and concurrent using a single line of Ruby!
+	n = 0
+	job = Dispatch::Job.new { n += 42 }
+	job.join
+	p n # => 0 
 
+====  Example: TBD
 
-	
-	
-=== Actor#__with__(group)
-	
-All invocations on the internal serial queue occur asynchronously. To keep track of them, pass it a group using the intrinsic (non-delegated) method +__with__+:
 
-	g = Dispatch::Group.new
-	ary.__with__(g)
-	Dispatch.async(g) { ary << Math.sqrt(10**100) }
-	Dispatch.async(g) { ary << Math.sqrt(10**1000) }
-	g.join { p ary.to_s }
 
 
 
-	puts "\n Use Dispatch.wrap to serialize object using an Actor"
-	b = Dispatch.wrap(Array)
-	b << "safely change me"
-	p b.size # => 1 (synchronous return)
-	b.size {|n| p "Size=#{n}"} # => "Size=1" (asynchronous return)
-
-
 == Iteration
 
 You use the default queue to run a single item in the background or to run many operations at once.  For the common case of a “parallel for loop”,  GCD provides an optimized “apply” function that submits a block for each iteration:

Deleted: MacRuby/trunk/lib/dispatch/actor.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/actor.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/actor.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -1,57 +0,0 @@
-require 'delegate'
-
-module Dispatch
-  # Serialize or asynchronize access to a delegate object.
-  # Forwards method invocations to the passed object via a private serial queue, 
-  # and optionally calls back asynchronously (if given a block or group).
-  #
-  # Note that this will NOT work for methods that themselves expect a block.
-  # For those, use e.g., the Enumerable p_* methods insteads. 
-  class Actor < SimpleDelegator
-    
-    # Create an Actor to wrap the given +delegate+,
-    # optionally specifying the default +callback+ queue
-    def initialize(delegate, callback=nil)
-      super(delegate)
-      @callback = callback || Dispatch::Queue.concurrent
-      @q = Dispatch::Queue.new("dispatch.actor.#{delegate}.#{object_id}")
-    end
-        
-    # Specify the +callback+ queue for async requests
-    # => self to allow chaining
-    def _on_(callback)
-      @callback = callback
-      self
-    end
-
-    # Specify or return a +group+ for private async requests
-    def __group__(group=nil)
-      @group = group  || Group.new
-    end
-
-    # Wait until the internal private queue has completed pending executions
-    # then return the +delegate+ object
-    def _done_
-      @q.sync { }
-      __getobj__
-    end
-
-    # Calls the +delegate+ object asychronously if there is a block or group,
-    # else synchronously
-    def method_missing(symbol, *args, &block)
-      if block_given? or not @group.nil?
-        callback = @callback
-        @q.async(@group) do
-          retval = __getobj__.__send__(symbol, *args)
-          callback.async { block.call(retval) }  if not block.nil?
-        end
-        return nil
-      else
-        @retval = nil
-        @q.sync { @retval = __getobj__.__send__(symbol, *args) }
-        return @retval
-      end
-    end
-
-  end
-end

Deleted: MacRuby/trunk/lib/dispatch/dispatch.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/dispatch.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/dispatch.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -1,52 +0,0 @@
-# Convenience methods for invoking GCD 
-# directly from the top-level Dispatch module
-
-module Dispatch
-
-  # Asynchronously run the +&block+ on a concurrent queue
-  # as part a +job+ -- which is returned for use with +join+ or +value+ 
-  def fork(job=nil, &block)
-    Dispatch::Queue.concurrent.fork(job) &block
-  end
-
-  # Returns a mostly unique reverse-DNS-style label based on
-  # the ancestor chain and ID of +obj+ plus the current time
-  # 
-  #   Dispatch.labelize(Array.new)
-  #   => Dispatch.enumerable.array.0x2000cc2c0.1265915278.97557
-  #
-  def labelize(obj)
-    names = obj.class.ancestors[0...-2].map {|a| a.to_s.downcase}
-    label = names.uniq.reverse.join(".")
-    "#{self}.#{label}.%p.#{Time.now.to_f}" % obj.object_id
-  end
-
-  # Returns a new serial queue with a unique label based on +obj+
-  # (or self, if no object is specified)
-  # used to serialize access to objects called from multiple threads
-  #
-  #   a = Array.new
-  #   q = Dispatch.queue(a)
-  #   q.async {a << 2 }
-  #
-  def queue(obj=self, &block)
-    q = Dispatch::Queue.new(Dispatch.labelize(obj))
-    q.async { block.call } if not block.nil?
-    q
-  end
-
-  # Wrap the passed +obj+ (or its instance, if a Class) inside an Actor
-  # to serialize access and allow asynchronous returns
-  #
-  #   a = Dispatch.wrap(Array)
-  #   a << Time.now # automatically serialized
-  #   a.size # => 1 (synchronous return)
-  #   a.size {|n| p "Size=#{n}"} # => "Size=1" (asynchronous return)
-  #
-  def wrap(obj)
-    Dispatch::Actor.new( (obj.is_a? Class) ? obj.new : obj)
-  end
-
-  module_function :fork, :labelize, :queue, :wrap
-
-end

Modified: MacRuby/trunk/lib/dispatch/enumerable.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/enumerable.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/enumerable.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -54,9 +54,9 @@
   # Parallel +collect+
   # Results match the order of the original array
   def p_map(stride=1, priority=nil,  &block)
-    result = Dispatch.wrap(Array)
+    result = Dispatch::Proxy.new([])
     self.p_each_with_index(stride, priority) { |obj, i| result[i] = block.call(obj) }
-    result._done_
+    result.__value__
   end
 
   # Parallel +collect+ plus +inject+
@@ -66,10 +66,10 @@
     # Check first, since exceptions from a Dispatch block can act funky 
     raise ArgumentError if not initial.respond_to? op
     # TODO: assign from within a Dispatch.once to avoid race condition
-    @mapreduce_q ||= Dispatch.queue(self)
+    @mapreduce_q ||= Dispatch::Queue.for(self)
     @mapreduce_q.sync do # in case called more than once at a time
       @mapreduce_result = initial
-      q = Dispatch.queue(@mapreduce_result)
+      q = Dispatch::Queue.for(@mapreduce_result)
       self.p_each do |obj|
         val = block.call(obj)
         q.async { @mapreduce_result = @mapreduce_result.send(op, val) }
@@ -82,9 +82,9 @@
   # Parallel +select+; will return array of objects for which
   # +&block+ returns true.
   def p_find_all(stride=1, priority=nil,  &block)
-    found_all = Dispatch.wrap(Array)
+    found_all = Dispatch::Proxy.new([])
     self.p_each(stride, priority) { |obj| found_all << obj if block.call(obj) }
-    found_all._done_
+    found_all.__value__
   end
 
   # Parallel +detect+; will return -one- match for +&block+
@@ -92,10 +92,10 @@
   # Only useful if the test block is very expensive to run
   # Note: each object can only run one p_find at a time
   def p_find(stride=1, priority=nil,  &block)
-    @find_q ||= Dispatch.queue(self)
+    @find_q ||= Dispatch::Queue.for(self)
     @find_q.sync do 
       @find_result = nil
-      q = Dispatch.queue(@find_result)
+      q = Dispatch::Queue.for(@find_result)
       self.p_each(stride, priority) do |obj|
         q.async { @find_result = obj } if @find_result.nil? and block.call(obj)
       end

Modified: MacRuby/trunk/lib/dispatch/job.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/job.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/job.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -4,27 +4,49 @@
   # Duck-type +join+ and +value+ from +Thread+
   class Job  
     # Create a Job that asynchronously dispatches the block 
-    attr_accessor :group
+    attr_reader :group, :results
     
     def initialize(queue = Dispatch::Queue.concurrent, &block)
-      @value = nil
+      @queue = queue
       @group = Group.new
-      queue.async(@group) { @value = block.call }
+      @results = synchronize([])
+      add(&block) if not block.nil?
     end
-
-    # Waits for the computation to finish, or calls block (if present) when done
-    def join(&block)
-      group.join(&block)
+    
+    def synchronize(obj)
+      Dispatch::Proxy.new(obj, @group)
     end
-
-    # Joins, then returns the value
-    # If a block is passed, invoke that asynchronously with the final value
+    
+    # Submit block as part of the same dispatch group
+    def add(&block)
+      @queue.async(@group) { @results << block.call }      
+    end
+  
+    # Wait until execution has completed.
+    # If a +block+ is passed, invoke that asynchronously
     # on the specified +queue+ (or else the default queue).
-    def value(queue = Dispatch::Queue.concurrent, &callback)
-      return group.notify(queue) { callback.call(@value) } if not callback.nil?
+    def join(queue = Dispatch::Queue.concurrent, &block)
+      return group.wait if block.nil?
+      group.notify(queue) { block.call } 
+    end
+  
+    # Wait then return the next value; note: only ordered if a serial queue
+    # If a +block+ is passed, invoke that asynchronously with the value
+    # on the specified +queue+ (or else the default queue).
+    def value(queue = Dispatch::Queue.concurrent, &block)
+      return group.notify(queue) { block.call(result) } if not block.nil?
       group.wait
-      return @value
+      return result
     end
+
+    alias_method :sync, :synchronize
+
+    private
+    
+    # Remove and return the first value    
+    def result
+       @results.shift
+    end
+    
   end
-
 end

Added: MacRuby/trunk/lib/dispatch/proxy.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/proxy.rb	                        (rev 0)
+++ MacRuby/trunk/lib/dispatch/proxy.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -0,0 +1,49 @@
+require 'delegate'
+
+module Dispatch
+  # Serialize or asynchronize access to a delegate object.
+  # Forwards method invocations to the passed object via a private serial queue, 
+  # and can call back asynchronously if given a block
+  #
+  class Proxy < SimpleDelegator
+    
+    attr_accessor :__group__, :__queue__, :__sync__
+    
+    # Create Proxy to wrap the given +delegate+,
+    # optionally specify +group+ and +queue+ for asynchronous callbacks
+    def initialize(delegate, group=Group.new, queue=Dispatch::Queue.concurrent)
+      super(delegate)
+      @__serial__ = Dispatch::Queue.for(self)
+      @__group__ = group
+      @__queue__ = queue
+      @__retval__ = nil
+    end
+
+    # Call methods on the +delegate+ object via a private serial queue
+    # Returns asychronously if given a block; else synchronously
+    #
+    def method_missing(symbol, *args, &block)
+      if block.nil? then
+        @__serial__.sync { @__retval__ = __getobj__.__send__(symbol,*args) }
+        return @__retval__
+      end
+      queue = @__queue__ # copy in case it changes while in flight
+      @__serial__.async(@__group__) do
+        retval = __getobj__.__send__(symbol, *args)
+        queue.async(@__group__) { block.call(retval) }
+      end
+    end
+
+    # Wait until the internal private queue has completed pending executions
+    def __wait__
+      @__serial__.sync { }
+    end
+    
+    # Return the +delegate+ object after waiting
+    def __value__
+      __wait__
+      __getobj__
+    end
+    
+  end
+end

Modified: MacRuby/trunk/lib/dispatch/queue.rb
===================================================================
--- MacRuby/trunk/lib/dispatch/queue.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch/queue.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -2,14 +2,29 @@
 
 module Dispatch
   class Queue
-    
-    def fork(job = nil, &block)
-      if job.nil?
-        Job.new block
-      else
-        async(job.group) { block.call }
-      end
+
+    # Returns a mostly unique reverse-DNS-style label based on
+    # the ancestor chain and ID of +obj+ plus the current time
+    # 
+    #   Dispatch::Queue.labelize(Array.new)
+    #   => enumerable.array.0x2000cc2c0.1265915278.97557
+    #
+    def self.labelize(obj)
+      names = obj.class.ancestors[0...-2].map {|a| a.to_s.downcase}
+      label = names.uniq.reverse.join(".")
+      "#{label}.0x%x.#{Time.now.to_f}" % obj.object_id
     end
+
+    # Returns a new serial queue with a unique label based on +obj+
+    # Typically used to serialize access to that object
+    #
+    #   a = Array.new
+    #   q = Dispatch::Queue.for(a)
+    #   q.async { a << 2 }
+    #
+    def self.for(obj)
+      new(labelize(obj))
+    end
     
   end
 end

Modified: MacRuby/trunk/lib/dispatch.rb
===================================================================
--- MacRuby/trunk/lib/dispatch.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/lib/dispatch.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -12,9 +12,8 @@
 # This library provides higher-level services and convenience methods
 # to make it easier for traditional Ruby programmers to add multicore support.
 
-require 'dispatch/actor'
-require 'dispatch/dispatch'
-require 'dispatch/enumerable'
-require 'dispatch/job'
-require 'dispatch/queue'
 require 'dispatch/queue_source'
+require 'dispatch/queue'
+require 'dispatch/proxy'
+require 'dispatch/job'
+require 'dispatch/enumerable'

Modified: MacRuby/trunk/sample-macruby/Scripts/gcd/dispatch_methods.rb
===================================================================
--- MacRuby/trunk/sample-macruby/Scripts/gcd/dispatch_methods.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/sample-macruby/Scripts/gcd/dispatch_methods.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -18,10 +18,10 @@
 f.value { |v| p "Returns #{v}" }
 sleep 0.1  # => "Returns 4"
 
-puts "\n Use Dispatch.queue to create a private serial queue"
+puts "\n Use Dispatch::Queue.for to create a private serial queue"
 puts "  - synchronizes access to shared data structures"
 a = Array.new
-q = Dispatch.queue(a)
+q = Dispatch::Queue.for(a)
 puts "  - has a (mostly) unique name:"
 p q # => Dispatch.enumerable.array.0x2000a6920.1266002369.9854
 q.async { a << "change me"  }
@@ -38,8 +38,8 @@
 g.notify(q) { p a }
 q.sync {} # => ["change me", "more change", "complex calculation"]
 
-puts "\n Use Dispatch.wrap to serialize object using an Actor"
-b = Dispatch.wrap(Array)
+puts "\n Use DispatchDispatch::Proxy.new to serialize object using an Actor"
+b = DispatchDispatch::Proxy.new(Array)
 b << "safely change me"
 p b.size # => 1 (synchronous return)
 b.size {|n| p "Size=#{n}"}

Modified: MacRuby/trunk/sample-macruby/Scripts/gcd/ring_buffer.rb
===================================================================
--- MacRuby/trunk/sample-macruby/Scripts/gcd/ring_buffer.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/sample-macruby/Scripts/gcd/ring_buffer.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -15,7 +15,7 @@
     attr_accessor :successor
     attr_reader :index
     def initialize(g, index, successor)
-        @queue = Dispatch.queue(self)
+        @queue = Dispatch::Queue.for(self)
         @group = g
         @index = index
         @successor = successor

Deleted: MacRuby/trunk/spec/macruby/library/dispatch/actor_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/actor_spec.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/spec/macruby/library/dispatch/actor_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -1,150 +0,0 @@
-require File.dirname(__FILE__) + "/../../spec_helper"
-require 'dispatch'
-
-if MACOSX_VERSION >= 10.6
-  
-  $global = 0
-  class Actee
-    def initialize(s); @s = s; end
-    def current_queue; Dispatch::Queue.current; end
-    def delay_set(n); sleep 0.01; $global = n; end
-    def increment(v); v+1; end
-    def to_s; @s; end
-  end
-  
-  describe "Dispatch::Actor" do
-    before :each do
-      @actee = Actee.new("my_actee")
-      @actor = Dispatch::Actor.new(@actee)
-    end
-    
-    describe :new do
-      it "should return an Actor when called with an actee" do
-        @actor.should be_kind_of(Dispatch::Actor)
-        @actor.should be_kind_of(SimpleDelegator)
-      end
-      
-      it "should set callback queue to use when called Asynchronously" do
-        qn = Dispatch::Queue.new("custom")
-        @actor2 = Dispatch::Actor.new(@actee, qn)
-        @qs = ""
-        @actor2.increment(41) {|rv| @qs = Dispatch::Queue.current.to_s}
-        while @qs == "" do; end
-        @qs.should == qn.label
-      end
-
-      it "should set default callback queue if not specified" do
-        @qs = ""
-        @actor.increment(41) {|rv| @qs = Dispatch::Queue.current.to_s}
-        while @qs == "" do; end
-        @qs.should =~ /com.apple.root.default/
-      end
-    end
-
-    describe :actee do
-      it "should be returned by __getobj__" do
-        @actee.should be_kind_of(Actee)
-        @actor.__getobj__.should be_kind_of(Actee)
-      end
-
-      it "should be invoked for most inherited methods" do
-        @actor.to_s.should == @actee.to_s
-      end
-    end
-
-    describe "method invocation" do
-      it "should occur on a private serial queue" do
-        q = @actor.current_queue
-        q.label.should =~ /dispatch.actor/
-      end
-
-      it "should be Synchronous if block is NOT given" do
-        $global = 0
-        @actor.delay_set(42)
-        $global.should == 42
-      end
-    
-      it "should be Asynchronous if block IS given" do
-        $global = 0
-        @actor.delay_set(42) { }
-        $global.should == 0
-        while $global == 0 do; end
-        $global.should == 42      
-      end
-    end
-
-    describe "return" do
-      it "should be value when called Synchronously" do
-        @actor.increment(41).should == 42
-      end
-
-      it "should be nil when called Asynchronously" do
-        @v = 0
-        v = @actor.increment(41) {|rv| @v = rv}
-        v.should.nil?
-      end
-
-      it "should pass value to block when called Asynchronously" do
-        @v = 0
-        @actor.increment(41) {|rv| @v = rv}
-        while @v == 0 do; end
-        @v.should == 42
-      end
-    end
-
-    describe :_on_ do
-      it "should specify callback queue used for actee async" do
-        qn = Dispatch::Queue.new("custom")
-        @qs = ""
-        @actor._on_(qn).increment(41) {|rv| @qs = Dispatch::Queue.current.to_s}
-        while @qs == "" do; end
-        @qs.should == qn.label
-      end
-    end
-
-    describe :__group__ do
-      it "should specify or create group used for actee async" do
-        $global = 0
-        g = @actor.__group__
-        @actor.delay_set(42)
-        $global.should == 0
-        g.wait
-        $global.should == 42      
-      end
-    end
-
-    describe :_done_ do
-      it "should complete work and return actee" do
-        $global = 0
-        @actor.delay_set(42) { }
-        $global.should == 0
-        actee = @actor._done_
-        $global.should == 42
-        actee.should == @actee     
-      end
-    end
-    
-    describe "state" do
-      it "should persist for collection objects" do
-        actor = Dispatch::Actor.new([])
-        actor.size.should == 0
-        actor << :foo
-        actor.size.should == 1
-        actor[42] = :foo
-        actor.size.should == 43        
-      end
-      
-      it "should persist for numbers" do
-        actor = Dispatch::Actor.new(0)
-        actor.to_i.should == 0
-        actor += 1
-        actor.to_i.should == 1
-        actor += 41.0
-        actor.to_i.should == 42
-        sum = actor - 1
-        sum.should == 41
-      end
-    end
-
-  end  
-end
\ No newline at end of file

Deleted: MacRuby/trunk/spec/macruby/library/dispatch/dispatch_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/dispatch_spec.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/spec/macruby/library/dispatch/dispatch_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -1,92 +0,0 @@
-require File.dirname(__FILE__) + "/../../spec_helper"
-require 'dispatch'
-
-if MACOSX_VERSION >= 10.6
-  
-  $dispatch_gval = 0
-  class Actee
-    def initialize(s="default"); @s = s; end
-    def delay_set(n); sleep 0.01; $dispatch_gval = n; end
-    def to_s; @s; end
-  end
-  
-  describe "Dispatch method" do
-    before :each do
-      @actee = Actee.new("my_actee")
-    end
-
-    describe :async do
-      it "should execute the block Asynchronously" do
-        $dispatch_gval = 0
-        Dispatch.async(:default) { @actee.delay_set(42) }
-        $dispatch_gval.should == 0
-        while $dispatch_gval == 0 do; end
-        $dispatch_gval.should == 42      
-      end
-    end
-    
-    describe :fork do
-      it "should return a Job for tracking execution of the passed block" do
-        $dispatch_gval = 0
-        g = Dispatch.fork { @actee.delay_set(42) }
-        $dispatch_gval.should == 0
-        g.should be_kind_of Dispatch::Job
-        g.join
-        $dispatch_gval.should == 42      
-      end
-    end
-    
-    describe :group do
-      it "should execute the block with the specified group" do
-        $dispatch_gval = 0
-        g = Dispatch::Group.new
-        Dispatch.group(g) { @actee.delay_set(42) }
-        $dispatch_gval.should == 0
-        g.wait
-        $dispatch_gval.should == 42      
-      end
-    end
-
-    describe :labelize do
-      it "should return a unique label for any object" do
-        s1 = Dispatch.labelize @actee
-        s2 = Dispatch.labelize @actee
-        s1.should_not == s2
-      end
-    end
-
-    describe :queue do
-      it "should return a dispatch queue" do
-        q = Dispatch.queue
-        q.should be_kind_of Dispatch::Queue
-      end
-
-      it "should return a unique queue for the same object" do
-        q1 = Dispatch.queue(@actee)
-        q2 = Dispatch.queue(@actee)
-        q1.should_not == q2
-      end
-
-      it "should return a unique label for each queue" do
-        q1 = Dispatch.queue(@actee)
-        q2 = Dispatch.queue(@actee)
-        q1.to_s.should_not == q2.to_s
-      end
-    end
-
-    describe :wrap do
-      it "should return an Actor wrapping an instance of a passed class" do
-        actor = Dispatch.wrap(Actee)
-        actor.should be_kind_of Dispatch::Actor
-        actor.to_s.should == "default"
-      end
-
-      it "should return an Actor wrapping any other object" do
-        actor = Dispatch.wrap(@actee)
-        actor.should be_kind_of Dispatch::Actor
-        actor.to_s.should == "my_actee"
-      end
-    end
-    
-  end
-end

Modified: MacRuby/trunk/spec/macruby/library/dispatch/enumerable_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/enumerable_spec.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/spec/macruby/library/dispatch/enumerable_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -9,7 +9,7 @@
         before :each do
           @count = 4
           @ary = Array.new
-          @p_ary = Dispatch.wrap(Array)
+          @p_ary = Dispatch::Proxy.new []
         end
 
         it "runs the block that many times" do
@@ -69,7 +69,7 @@
           @ary1 = 0
           @ary.each {|v| @ary1 << v*v}
           @ary2 = 0
-          @q = Dispatch.queue(@ary2)
+          @q = Dispatch::Queue.for(@ary2)
           @ary.p_each {|v| temp = v*v; @q.sync {@ary2 << temp} }
           @ary2.should == @ary1
         end
@@ -92,7 +92,7 @@
           @ary1 = 0
           @ary.each_with_index {|v, i| @ary1 << v**i}
           @ary2 = 0
-          @q = Dispatch.queue(@ary2)
+          @q = Dispatch::Queue.for(@ary2)
           @ary.p_each_with_index {|v, i| temp = v**i; @q.sync {@ary2 << temp} }
           @ary2.should == @ary1
         end

Modified: MacRuby/trunk/spec/macruby/library/dispatch/job_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/job_spec.rb	2010-02-23 23:27:30 UTC (rev 3594)
+++ MacRuby/trunk/spec/macruby/library/dispatch/job_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -6,62 +6,98 @@
     
     before :each do
       @result = 0
-      @job = Dispatch::Job.new { sleep 0.01; @result = Math.sqrt(2**10) }
+      @job = Dispatch::Job.new
     end
 
     describe :new do
-      it "should return a Job for tracking execution of the passed block" do
+      it "should return a Job for tracking execution of passed blocks" do
         @job.should be_kind_of Dispatch::Job
       end
 
-      it "should take a +priority+ for which concurrent queue to use" do
-        Job = Dispatch::Job.new(:high) { @result=Dispatch::Queue.current }
-        Job.join
-        @result.to_s.should == Dispatch::Queue.concurrent(:high).to_s
+      it "should use default queue" do
+        @job.add { Dispatch::Queue.current }
+        @job.value.to_s.should == Dispatch::Queue.concurrent.to_s
       end
+
+      it "should take an optional queue" do
+        q = Dispatch::Queue.concurrent(:high)
+        job = Dispatch::Job.new(q) { Dispatch::Queue.current }
+        job.value.to_s.should == q.to_s
+      end
     end
-
+    
     describe :group do
       it "should return an instance of Dispatch::Group" do
         @job.group.should be_kind_of Dispatch::Group
       end
     end
 
-    describe :join do
-      it "should wait until execution is complete if no block is passed" do
-        @q.async(@g) { @i = 42 }
-        @g.join
-        @i.should == 42
+    describe :results do
+      it "should return an instance of Dispatch::Proxy" do
+        @job.results.should be_kind_of Dispatch::Proxy
       end
 
-      it "should run a notify block on completion, if passed" do
-        @q.async(@g) { @i = 42 }
-        @g.join {@i *= 2}
-        while @i <= 42 do; end    
-        @i.should == 84
+      it "has a __value__ that is Enumerable" do
+        @job.results.__value__.should be_kind_of Enumerable
       end
+    end
 
-      it "should run notify block on specified queue, if any" do
-        @q.async(@g) { @i = 42 }
-        @q.sync { }
-        @g.join(@q) {@i *= 2}
-        @q.sync { }
-        @i.should == 84
+    describe :add do
+      it "should schedule a block for async execution" do
+        @value = 0
+        @job.add { sleep 0.01; @value = 42 }
+        @value.should == 0
+        @job.join
+        @value.should == 42
       end
     end
+    
+    describe :join do
+      it "should wait when called Synchronously" do
+        @value = 0
+        @job.add { sleep 0.01; @value = 42 }
+        @job.join
+        @value.should == 42
+      end
 
+      it "should invoke passed block Asynchronously" do
+        @value = 0
+        @rval = 0
+        @job.add { sleep 0.01; @value = 42 }
+        q = Dispatch::Queue.for(@value)
+        @job.join(q) { sleep 0.01; @rval = @value }
+        @job.join
+        @rval.should == 0
+        q.sync { }
+        @rval.should == 42
+      end
+    end
+
     describe :value do
       it "should return value when called Synchronously" do
+        @job.add { Math.sqrt(2**10) }
         @job.value.should == 2**5
       end
 
       it "should invoke passed block Asynchronously with return value" do
-        @fval = 0
-        @job.value {|v| @fval = v}
-        while @fval == 0 do; end
-        @fval.should == 2**5
+        @job.add { Math.sqrt(2**10) }
+        @value = 0
+        q = Dispatch::Queue.for(@value)
+        @job.value(q) {|v| @value = v}
+        q.sync { }
+        @value.should == 2**5
       end
     end
-    
+
+    describe :synchronize do
+      it "should return serialization Proxy for passed object" do
+        actee = {}
+        actor = @job.sync(actee)
+        actor.should be_kind_of Dispatch::Proxy
+        actor.__value__.should == actee
+      end
+
+    end
+
   end
 end
\ No newline at end of file

Added: MacRuby/trunk/spec/macruby/library/dispatch/proxy_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/proxy_spec.rb	                        (rev 0)
+++ MacRuby/trunk/spec/macruby/library/dispatch/proxy_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -0,0 +1,106 @@
+require File.dirname(__FILE__) + "/../../spec_helper"
+require 'dispatch'
+
+if MACOSX_VERSION >= 10.6
+  
+  class Delegate
+    def initialize(s); @s = s; end
+    def current_queue; Dispatch::Queue.current; end
+    def takes_block(&block); block.call; end
+    def get_number(); sleep 0.01; 42; end
+    def set_name(s); sleep 0.01; @s = s; end
+    def to_s; @s.to_s; end
+  end
+  
+  describe "Dispatch::Proxy" do
+    before :each do
+      @delegate_name = "my_delegate"
+      @delegate = Delegate.new(@delegate_name)
+      @proxy = Dispatch::Proxy.new(@delegate)
+    end
+    
+    describe :new do
+      it "returns a Dispatch::Proxy" do
+        @proxy.should be_kind_of Dispatch::Proxy
+        @proxy.should be_kind_of SimpleDelegator
+      end
+
+      it "takes an optional group and queue for callbacks" do
+        g = Dispatch::Group.new
+        q = Dispatch::Queue.concurrent(:high)
+        proxy = Dispatch::Proxy.new(@delegate, g, q)
+        proxy.__group__.should == g
+        proxy.__queue__.should == q
+      end
+
+      it "creates a default Group if none specified" do
+        @proxy.__group__.should be_kind_of Dispatch::Group
+      end
+
+      it "uses default queue if none specified" do
+        @proxy.__queue__.should == Dispatch::Queue.concurrent
+      end
+    end
+
+    describe :delegate do
+      it "should be returned by __getobj__" do
+        @proxy.__getobj__.should == @delegate
+      end
+
+      it "should be invoked for methods it defines" do
+        @proxy.to_s.should == @delegate_name
+      end
+    end
+
+    describe :method_missing do
+      it "runs methods on a private serial queue" do
+        q = @proxy.current_queue
+        q.label.should =~ /proxy/
+      end
+
+      it "should return Synchronously if block NOT given" do
+        retval = @proxy.get_number
+        retval.should == 42
+      end
+    
+      it "should otherwise return Asynchronous to block, if given" do
+        @value = 0
+        retval = @proxy.get_number { |v| @value = v }
+        @value.should == 0      
+        retval.should == nil
+        @proxy.__group__.wait
+        @value.should == 42      
+      end
+    end
+
+    describe :__value__ do
+      it "should complete work and return delegate" do
+        new_name = "nobody"
+        @proxy.set_name(new_name) { }
+        d = @proxy.__value__
+        d.should be_kind_of Delegate
+        d.to_s.should == new_name
+      end
+    end
+    
+    describe "state" do
+      it "should persist for collection objects" do
+        actor = Dispatch::Proxy.new([])
+        actor.size.should == 0
+        actor << :foo
+        actor.size.should == 1
+        actor[42] = :foo
+        actor.size.should == 43        
+        actor.should be_kind_of Dispatch::Proxy
+      end
+      
+      it "should NOT persist under assignment" do
+        actor = Dispatch::Proxy.new(0)
+        actor.should be_kind_of Dispatch::Proxy
+        actor += 1
+        actor.should_not be_kind_of Dispatch::Proxy
+      end
+    end
+
+  end  
+end
\ No newline at end of file

Added: MacRuby/trunk/spec/macruby/library/dispatch/queue_spec.rb
===================================================================
--- MacRuby/trunk/spec/macruby/library/dispatch/queue_spec.rb	                        (rev 0)
+++ MacRuby/trunk/spec/macruby/library/dispatch/queue_spec.rb	2010-02-23 23:51:06 UTC (rev 3595)
@@ -0,0 +1,38 @@
+require File.dirname(__FILE__) + "/../../spec_helper"
+require 'dispatch'
+
+if MACOSX_VERSION >= 10.6
+  
+  describe "Dispatch::Queue" do
+    before :each do
+      @my_object = "Hello, World!"
+    end
+
+    describe :labelize do
+      it "should return a unique label for any object" do
+        s1 = Dispatch::Queue.labelize @my_object
+        s2 = Dispatch::Queue.labelize @my_object
+        s1.should_not == s2
+      end
+    end
+
+    describe :for do
+      it "should return a dispatch queue" do
+        q = Dispatch::Queue.for(@my_object)
+        q.should be_kind_of Dispatch::Queue
+      end
+
+      it "should return a unique queue for each object" do
+        q1 = Dispatch::Queue.for(@my_object)
+        q2 = Dispatch::Queue.for(@my_object)
+        q1.should_not == q2
+      end
+
+      it "should return a unique label for each queue" do
+        q1 = Dispatch::Queue.for(@my_object)
+        q2 = Dispatch::Queue.for(@my_object)
+        q1.to_s.should_not == q2.to_s
+      end
+    end
+  end
+end
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20100223/44e78f1e/attachment-0001.html>


More information about the macruby-changes mailing list