Example #1
0
        /// <summary> Return active status of t.
        /// Per-thread active status can only be accessed and
        /// modified via synchronized method here in the group class.
        ///
        /// </summary>

        protected internal virtual bool GetActive(FJTaskRunner t)
        {
            lock (this)
            {
                return(t.active);
            }
        }
Example #2
0
        /* ------------ Methods called only by FJTaskRunners ------------- */


        /// <summary> Return a task from entry queue, or null if empty.
        /// Called only by FJTaskRunner.scan().
        ///
        /// </summary>
        protected internal virtual FJTask PollEntryQueue(FJTaskRunner runner)
        {
            try
            {
                object o = entryQueue.Poll(0);
                if (o is FJTask)
                {
                    return(o as FJTask);
                }
                else if (o is InvokableCandidateFJTask)
                {
                    InvokableCandidateFJTask i = o as InvokableCandidateFJTask;
                    return(i.AsInvokableFJTask(runner));
                }
                else if (o is IRunnable)
                {
                    return(new FJTask.Wrap(o as IRunnable));
                }
                else if (o == null)
                {
                    return(null);
                }
                else
                {
                    throw new ArgumentException("unexpected object in queue", o != null ? o.GetType().Name : "null");
                }
            }
            catch (System.Threading.ThreadInterruptedException)
            {
                // ignore interrupts
                ThreadClass.Current.Interrupt();
                return(null);
            }
        }
Example #3
0
        /// <summary> Set active status of thread t to false, and
        /// then wait until: (a) there is a task in the entry
        /// queue, or (b) other threads are active, or (c) the current
        /// thread is interrupted. Upon return, it
        /// is not certain that there will be work available.
        /// The thread must itself check.
        /// <p>
        /// The main underlying reason
        /// for these mechanics is that threads do not
        /// signal each other when they add elements to their queues.
        /// (This would add to task overhead, reduce locality.
        /// and increase contention.)
        /// So we must rely on a tamed form of polling. However, tasks
        /// inserted into the entry queue do result in signals, so
        /// tasks can wait on these if all of them are otherwise idle.
        /// </p>
        /// </summary>

        protected internal virtual void  CheckActive(FJTaskRunner t, long scans)
        {
            lock (this)
            {
                Inactive = t;

                try
                {
                    // if nothing available, do a hard wait
                    if (activeCount_ == 0 && entryQueue.Peek() == null)
                    {
                        System.Threading.Monitor.Wait(this);
                    }
                    else
                    {
                        // If there is possibly some work,
                        // sleep for a while before rechecking

                        long msecs = scans / ScansPerSleep;
                        if (msecs > MaxSleepTime)
                        {
                            msecs = MaxSleepTime;
                        }
                        int nsecs = (msecs == 0)?1:0;                         // forces shortest possible sleep
                        System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(msecs + (nsecs / 1000)));
                    }
                }
                catch (System.Threading.ThreadInterruptedException)
                {
                    System.Threading.Monitor.Pulse(this);                     // avoid lost notifies on interrupts
                    ThreadClass.Current.Interrupt();
                }
            }
        }
Example #4
0
        /// <summary> Create all FJTaskRunner threads in this group.
        ///
        /// </summary>

        protected internal virtual void  InitializeThreads()
        {
            for (int i = 0; i < threads.Length; ++i)
            {
                threads[i] = FJTaskRunner.New(this);
            }
        }
Example #5
0
        /// <summary>
        /// Create a new <see cref="FJTaskRunner"/>, setting its thread
        /// as needed
        /// </summary>
        public static FJTaskRunner New(FJTaskRunnerGroup runnerGroup)
        {
            FJTaskRunner runner = new FJTaskRunner(runnerGroup);
            Thread       t      = new Thread(new ThreadStart(runner.DoStart));

            t.Name = "FJTaskRunner thread #" + t.GetHashCode();
            runner.SetThread(t);
            return(runner);
        }
Example #6
0
 public FJTask AsInvokableFJTask(FJTaskRunner runner)
 {
     lock (group)
     {
         if (invokableFJTask == null)
         {
             invokableFJTask = new InvokableFJTask(r, group);
         }
         return(invokableFJTask);
     }
 }
Example #7
0
        /// <summary> Prints various snapshot statistics to System.out.
        /// <ul>
        /// <li> For each FJTaskRunner thread (labeled as T<em>n</em>, for
        /// <em>n</em> from zero to group size - 1):</li>
        /// <ul>
        /// <li> A star "*" is printed if the thread is currently active;
        /// that is, not sleeping while waiting for work. Because
        /// threads gradually enter sleep modes, an active thread
        /// may in fact be about to sleep (or wake up).</li>
        /// <li> <em>Q Cap</em> The current capacity of its task queue.</li>
        /// <li> <em>Run</em> The total number of tasks that have been run.</li>
        /// <li> <em>New</em> The number of these tasks that were
        /// taken from either the entry queue or from other
        /// thread queues; that is, the number of tasks run
        /// that were <em>not</em> forked by the thread itself.</li>
        /// <li> <em>Scan</em> The number of times other task</li>
        /// queues or the entry queue were polled for tasks.
        /// </ul>
        /// <li> <em>Execute</em> The total number of tasks entered
        /// (but not necessarily yet run) via execute or invoke.</li>
        /// <li> <em>Time</em> Time in seconds since construction of this
        /// FJTaskRunnerGroup.</li>
        /// <li> <em>Rate</em> The total number of tasks processed
        /// per second across all threads. This
        /// may be useful as a simple throughput indicator
        /// if all processed tasks take approximately the
        /// same time to run.</li>
        /// </ul>
        /// <p>
        /// Cautions: Some statistics are updated and gathered
        /// without synchronization,
        /// so may not be accurate. However, reported counts may be considered
        /// as lower bounds of actual values.
        /// Some values may be zero if classes are compiled
        /// with COLLECT_STATS set to false. (FJTaskRunner and FJTaskRunnerGroup
        /// classes can be independently compiled with different values of
        /// COLLECT_STATS.) Also, the counts are maintained as ints so could
        /// overflow in exceptionally long-lived applications.
        /// </p>
        /// <p>
        /// These statistics can be useful when tuning algorithms or diagnosing
        /// problems. For example:
        /// </p>
        /// <ul>
        /// <li> High numbers of scans may mean that there is insufficient
        /// parallelism to keep threads busy. However, high scan rates
        /// are expected if the number
        /// of Executes is also high or there is a lot of global
        /// synchronization in the application, and the system is not otherwise
        /// busy. Threads may scan
        /// for work hundreds of times upon startup, shutdown, and
        /// global synch points of task sets.</li>
        /// <li> Large imbalances in tasks run across different threads might
        /// just reflect contention with unrelated threads on a system
        /// (possibly including JVM threads such as GC), but may also
        /// indicate some systematic bias in how you generate tasks.</li>
        /// <li> Large task queue capacities may mean that too many tasks are being
        /// generated before they can be run.
        /// Capacities are reported rather than current numbers of tasks
        /// in queues because they are better indicators of the existence
        /// of these kinds of possibly-transient problems.
        /// Queue capacities are
        /// resized on demand from their initial value of 4096 elements,
        /// which is much more than sufficient for the kinds of
        /// applications that this framework is intended to best support.</li>
        /// </ul>
        /// </summary>

        public virtual void  Stats(TextWriter writer)
        {
            long time = Utils.CurrentTimeMillis - initTime;
            //TODO: WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"'
            double secs        = ((double)time) / 1000.0;
            long   totalRuns   = 0;
            long   totalScans  = 0;
            long   totalSteals = 0;

            writer.Write("Thread" + "\tQ Cap" + "\tScans" + "\tNew" + "\tRuns" + "\n");

            for (int i = 0; i < threads.Length; ++i)
            {
                FJTaskRunner t     = threads[i];
                int          truns = t.runs;
                totalRuns += truns;

                int tscans = t.scans;
                totalScans += tscans;

                int tsteals = t.steals;
                totalSteals += tsteals;

                System.String star = (GetActive(t))?"*":" ";


                writer.Write("T" + i + star + "\t" + t.deqSize() + "\t" + tscans + "\t" + tsteals + "\t" + truns + "\n");
            }

            writer.Write("Total" + "\t    " + "\t" + totalScans + "\t" + totalSteals + "\t" + totalRuns + "\n");

            writer.Write("Execute: " + entries);

            writer.Write("\tTime: " + secs);

            long rps = 0;

            if (secs != 0)
            {
                //TODO: WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"'
                rps = (long)System.Math.Round((double)(totalRuns) / secs);
            }

            writer.WriteLine("\tRate: " + rps);
        }
Example #8
0
        /// <summary> Set active status of thread t to true, and notify others
        /// that might be waiting for work.
        ///
        /// </summary>

        protected internal virtual void  SetActive(FJTaskRunner t)
        {
            lock (this)
            {
                if (!t.active)
                {
                    t.active = true;
                    ++activeCount_;
                    if (nstarted < threads.Length)
                    {
                        threads[nstarted++].Start();
                    }
                    else
                    {
                        System.Threading.Monitor.PulseAll(this);
                    }
                }
            }
        }
Example #9
0
 /// <summary>
 /// Create a new <see cref="FJTaskRunner"/>, setting its thread
 /// as needed
 /// </summary>
 public static FJTaskRunner New(FJTaskRunnerGroup runnerGroup)
 {
     FJTaskRunner runner = new FJTaskRunner(runnerGroup);
     Thread t = new Thread(new ThreadStart(runner.DoStart));
     t.Name = "FJTaskRunner thread #" + t.GetHashCode();
     runner.SetThread(t);
     return runner;
 }
Example #10
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke task1 and task2, in parallel
 ///
 /// </summary>
 public static FJTask NewPar(FJTask task1, FJTask task2, FJTaskRunner fjTaskRunner)
 {
     return(new Par2(task1, task2));
 }
Example #11
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke the tasks in the tasks array in parallel using coInvoke
 ///
 /// </summary>
 public static FJTask NewPar(FJTask[] tasks, FJTaskRunner runner)
 {
     return(new Par(tasks));
 }
Example #12
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke the tasks in the tasks array in array order
 /// </summary>
 public static FJTask NewSeq(FJTask[] tasks, FJTaskRunner fjTaskRunner)
 {
     return(new Seq(tasks));
 }
Example #13
0
        /// <summary> Fork all tasks in array, and await their completion.
        /// Behaviorally equivalent to:
        /// <pre>
        /// for (int i = 0; i &lt; tasks.length; ++i) tasks[i].fork();
        /// for (int i = 0; i &lt; tasks.length; ++i) tasks[i].join();
        /// </pre>
        ///
        /// </summary>

        public void  CoInvoke(FJTask[] tasks)
        {
            FJTaskRunner.coInvoke(tasks);
        }
Example #14
0
        /* ------------ Scheduling  ------------------- */


        /// <summary> Do all but the pop() part of yield or join, by
        /// traversing all DEQs in our group looking for a task to
        /// steal. If none, it checks the entry queue.
        /// <p>
        /// Since there are no good, portable alternatives,
        /// we rely here on a mixture of Thread.yield and priorities
        /// to reduce wasted spinning, even though these are
        /// not well defined. We are hoping here that the JVM
        /// does something sensible.
        /// </p>
        /// </summary>
        /// <param name="waitingFor">if non-null, the current task being joined
        ///
        /// </param>

        protected internal virtual void  scan(FJTask waitingFor)
        {
            FJTask task = null;

            // to delay lowering priority until first failure to steal
            bool lowered = false;

            /*
             * Circularly traverse from a random start index.
             *
             * This differs slightly from cilk version that uses a random index
             * for each attempted steal.
             * Exhaustive scanning might impede analytic tractablity of
             * the scheduling policy, but makes it much easier to deal with
             * startup and shutdown.
             */

            FJTaskRunner[] ts  = group_.Array;
            int            idx = victimRNG.Next(ts.Length);

            for (int i = 0; i < ts.Length; ++i)
            {
                FJTaskRunner t = ts[idx];
                if (++idx >= ts.Length)
                {
                    idx = 0;                     // circularly traverse
                }
                if (t != null && t != this)
                {
                    if (waitingFor != null && waitingFor.Done)
                    {
                        break;
                    }
                    else
                    {
                        if (CollectStats)
                        {
                            ++scans;
                        }
                        task = t.take();
                        if (task != null)
                        {
                            if (CollectStats)
                            {
                                ++steals;
                            }
                            break;
                        }
                        else
                        {
                            if (Interrupted)
                            {
                                break;
                            }
                            else if (!lowered)
                            {
                                // if this is first fail, lower priority
                                lowered  = true;
                                Priority = (ThreadPriority)scanPriority_;
                            }
                            else
                            {
                                // otherwise we are at low priority; just yield
                                Thread.Sleep(0);
                            }
                        }
                    }
                }
            }

            if (task == null)
            {
                if (CollectStats)
                {
                    ++scans;
                }
                task = group_.PollEntryQueue(this);
                if (CollectStats)
                {
                    if (task != null)
                    {
                        ++steals;
                    }
                }
            }

            if (lowered)
            {
                Priority = (ThreadPriority)runPriority_;
            }

            if (task != null && !task.Done)
            {
                if (CollectStats)
                {
                    ++runs;
                }
                task.Run();
                task.SetDone();
            }
        }
Example #15
0
 /// <summary> Set active status of thread t to true, and notify others
 /// that might be waiting for work. 
 /// 
 /// </summary>
 protected internal virtual void SetActive(FJTaskRunner t)
 {
     lock (this)
     {
         if (!t.active)
         {
             t.active = true;
             ++activeCount_;
             if (nstarted < threads.Length)
                 threads[nstarted++].Start();
             else
                 System.Threading.Monitor.PulseAll(this);
         }
     }
 }
Example #16
0
 /// <summary> Return active status of t.
 /// Per-thread active status can only be accessed and
 /// modified via synchronized method here in the group class.
 /// 
 /// </summary>
 protected internal virtual bool GetActive(FJTaskRunner t)
 {
     lock (this)
     {
         return t.active;
     }
 }
Example #17
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke task1 and task2, in order
 /// 
 /// </summary>
 public static FJTask NewSeq(FJTask task1, FJTask task2, FJTaskRunner fjTaskRunner)
 {
     return new Seq2(task1, task2);
 }
Example #18
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke the tasks in the tasks array in parallel using coInvoke
 /// 
 /// </summary>
 public static FJTask NewPar(FJTask[] tasks, FJTaskRunner runner)
 {
     return new Par(tasks);
 }
Example #19
0
        /// <summary> Same as scan, but called when current thread is idling.
        /// It repeatedly scans other threads for tasks,
        /// sleeping while none are available.
        /// <p>
        /// This differs from scan mainly in that
        /// since there is no reason to return to recheck any
        /// condition, we iterate until a task is found, backing
        /// off via sleeps if necessary.
        /// </p>
        ///
        /// </summary>

        protected internal virtual void  scanWhileIdling()
        {
            FJTask task = null;

            bool lowered = false;
            long iters   = 0;

            FJTaskRunner[] ts  = group_.Array;
            int            idx = victimRNG.Next(ts.Length);

            do
            {
                for (int i = 0; i < ts.Length; ++i)
                {
                    FJTaskRunner t = ts[idx];
                    if (++idx >= ts.Length)
                    {
                        idx = 0;                         // circularly traverse
                    }
                    if (t != null && t != this)
                    {
                        if (CollectStats)
                        {
                            ++scans;
                        }

                        task = t.take();
                        if (task != null)
                        {
                            if (CollectStats)
                            {
                                ++steals;
                            }
                            if (lowered)
                            {
                                Priority = (ThreadPriority)runPriority_;
                            }
                            group_.SetActive(this);
                            break;
                        }
                    }
                }

                if (task == null)
                {
                    if (Interrupted)
                    {
                        return;
                    }

                    if (CollectStats)
                    {
                        ++scans;
                    }
                    task = group_.PollEntryQueue(this);

                    if (task != null)
                    {
                        if (CollectStats)
                        {
                            ++steals;
                        }
                        if (lowered)
                        {
                            Priority = (ThreadPriority)runPriority_;
                        }
                        group_.SetActive(this);
                    }
                    else
                    {
                        ++iters;
                        //  Check here for yield vs sleep to avoid entering group synch lock
                        if (iters >= FJTaskRunnerGroup.ScansPerSleep)
                        {
                            group_.CheckActive(this, iters);
                            if (Interrupted)
                            {
                                return;
                            }
                        }
                        else if (!lowered)
                        {
                            lowered  = true;
                            Priority = (ThreadPriority)scanPriority_;
                        }
                        else
                        {
                            Thread.Sleep(0);
                        }
                    }
                }
            }while (task == null);


            if (!task.Done)
            {
                if (CollectStats)
                {
                    ++runs;
                }
                task.Run();
                task.SetDone();
            }
        }
Example #20
0
 /// <summary> Construct and return a FJTask object that, when executed, will
 /// invoke the tasks in the tasks array in array order
 /// </summary>		
 public static FJTask NewSeq(FJTask[] tasks, FJTaskRunner fjTaskRunner)
 {
     return new Seq(tasks);
 }
Example #21
0
        /// <summary> Arrange for execution of a strictly dependent task.
        /// The task that will be executed in
        /// procedure-call-like LIFO order if executed by the
        /// same worker thread, but is FIFO with respect to other tasks
        /// forked by this thread when taken by other worker threads.
        /// That is, earlier-forked
        /// tasks are preferred to later-forked tasks by other idle workers.
        /// <p>
        /// Fork() is noticeably
        /// faster than start(). However, it may only
        /// be used for strictly dependent tasks -- generally, those that
        /// could logically be issued as straight method calls without
        /// changing the logic of the program.
        /// The method is optimized for use in parallel fork/join designs
        /// in which the thread that issues one or more forks
        /// cannot continue until at least some of the forked
        /// threads terminate and are joined.
        /// </p>
        /// </summary>

        public virtual void  Fork()
        {
            FJTaskRunner.push(this);
        }
Example #22
0
        /// <summary> Set active status of thread t to false, and
        /// then wait until: (a) there is a task in the entry 
        /// queue, or (b) other threads are active, or (c) the current
        /// thread is interrupted. Upon return, it
        /// is not certain that there will be work available.
        /// The thread must itself check. 
        /// <p>
        /// The main underlying reason
        /// for these mechanics is that threads do not
        /// signal each other when they add elements to their queues.
        /// (This would add to task overhead, reduce locality.
        /// and increase contention.)
        /// So we must rely on a tamed form of polling. However, tasks
        /// inserted into the entry queue do result in signals, so
        /// tasks can wait on these if all of them are otherwise idle.
        /// </p>
        /// </summary>
        protected internal virtual void CheckActive(FJTaskRunner t, long scans)
        {
            lock (this)
            {

                Inactive = t;

                try
                {
                    // if nothing available, do a hard wait
                    if (activeCount_ == 0 && entryQueue.Peek() == null)
                    {
                        System.Threading.Monitor.Wait(this);
                    }
                    else
                    {
                        // If there is possibly some work,
                        // sleep for a while before rechecking

                        long msecs = scans / ScansPerSleep;
                        if (msecs > MaxSleepTime)
                            msecs = MaxSleepTime;
                        int nsecs = (msecs == 0)?1:0; // forces shortest possible sleep
                        System.Threading.Monitor.Wait(this, TimeSpan.FromMilliseconds(msecs + (nsecs / 1000)));
                    }
                }
                catch (System.Threading.ThreadInterruptedException)
                {
                    System.Threading.Monitor.Pulse(this); // avoid lost notifies on interrupts
                    ThreadClass.Current.Interrupt();
                }
            }
        }
Example #23
0
 /// <summary> Allow the current underlying FJTaskRunner thread to process other tasks.
 /// <p>
 /// Spinloops based on yield() are well behaved so long
 /// as the event or condition being waited for is produced via another
 /// FJTask. Additionally, you must never hold a lock
 /// while performing a yield or join. (This is because
 /// multiple FJTasks can be run by the same Thread during
 /// a yield. Since java locks are held per-thread, the lock would not
 /// maintain the conceptual exclusion you have in mind.)
 /// </p>
 /// <p>
 /// Otherwise, spinloops using
 /// yield are the main construction of choice when a task must wait
 /// for a condition that it is sure will eventually occur because it
 /// is being produced by some other FJTask.  The most common
 /// such condition is built-in: join() repeatedly yields until a task
 /// has terminated after producing some needed results. You can also
 /// use yield to wait for callbacks from other FJTasks, to wait for
 /// status flags to be set, and so on. However, in all these cases,
 /// you should be confident that the condition being waited for will
 /// occur, essentially always because it is produced by
 /// a FJTask generated by the current task, or one of its subtasks.
 /// </p>
 /// </summary>
 public void  Yield()
 {
     FJTaskRunner.taskYield();
 }
Example #24
0
 /* ------------ Methods called only by FJTaskRunners ------------- */
 /// <summary> Return a task from entry queue, or null if empty.
 /// Called only by FJTaskRunner.scan().
 /// 
 /// </summary>		
 protected internal virtual FJTask PollEntryQueue(FJTaskRunner runner)
 {
     try
     {
         object o = entryQueue.Poll(0);
         if (o is FJTask)
         {
             return o as FJTask;
         }
         else if (o is InvokableCandidateFJTask)
         {
             InvokableCandidateFJTask i = o as InvokableCandidateFJTask;
             return i.AsInvokableFJTask(runner);
         }
         else if (o is IRunnable)
         {
             return new FJTask.Wrap(o as IRunnable);
         }
         else if (o == null)
         {
             return null;
         }
         else
         {
             throw new ArgumentException("unexpected object in queue", o != null ? o.GetType().Name : "null");
         }
     }
     catch (System.Threading.ThreadInterruptedException)
     {
         // ignore interrupts
         ThreadClass.Current.Interrupt();
         return null;
     }
 }
Example #25
0
 /// <summary> Yield until this task isDone.
 /// Equivalent to <code>while(!isDone()) yield(); </code>
 /// </summary>
 public virtual void  Join()
 {
     FJTaskRunner.taskJoin(this);
 }
Example #26
0
 public FJTask AsInvokableFJTask(FJTaskRunner runner)
 {
     lock (group)
     {
         if (invokableFJTask == null)
         {
             invokableFJTask = new InvokableFJTask(r, group);
         }
         return invokableFJTask;
     }
 }
Example #27
0
 /// <summary> Fork both tasks and then wait for their completion. It behaves as:
 /// <pre>
 /// task1.fork(); task2.fork(); task2.join(); task1.join();
 /// </pre>
 /// As a simple classic example, here is
 /// a class that computes the Fibonacci function:
 /// <pre>
 /// public class Fib extends FJTask {
 ///
 /// // Computes fibonacci(n) = fibonacci(n-1) + fibonacci(n-2);  for n> 1
 /// //          fibonacci(0) = 0;
 /// //          fibonacci(1) = 1.
 ///
 /// // Value to compute fibonacci function for.
 /// // It is replaced with the answer when computed.
 /// private volatile int number;
 ///
 /// public Fib(int n) { number = n; }
 ///
 /// public int getAnswer() {
 /// if (!isDone()) throw new Error("Not yet computed");
 /// return number;
 /// }
 ///
 /// public void run() {
 /// int n = number;
 /// if (n > 1) {
 /// Fib f1 = new Fib(n - 1);
 /// Fib f2 = new Fib(n - 2);
 ///
 /// coInvoke(f1, f2); // run these in parallel
 ///
 /// // we know f1 and f2 are computed, so just directly access numbers
 /// number = f1.number + f2.number;
 /// }
 /// }
 ///
 /// public static void main(String[] args) { // sample driver
 /// try {
 /// int groupSize = 2;    // 2 worker threads
 /// int num = 35;         // compute fib(35)
 /// FJTaskRunnerGroup group = new FJTaskRunnerGroup(groupSize);
 /// Fib f = new Fib(num);
 /// group.invoke(f);
 /// int result = f.getAnswer();
 /// System.out.println(" Answer: " + result);
 /// }
 /// catch (InterruptedException ex) {
 /// System.out.println("Interrupted");
 /// }
 /// }
 /// }
 /// </pre>
 ///
 /// </summary>
 public void  CoInvoke(FJTask task1, FJTask task2)
 {
     FJTaskRunner.coInvoke(task1, task2);
 }