具有返回类型的方法的线程池

时间:2012-06-16 03:58:59

标签: c#

我有一个名为InitializeCRMService()的方法,它返回IOrganizationService的对象。现在我定义了一个名为GetConnection(string thread)的不同方法,它根据传递给它的参数调用InitializeCRMService()。如果传递给GetConnection的字符串是单一的,它将启动IntializeCRMService()方法的单线程实例,但如果传递的字符串是多个,我需要使用一个线程池,我需要传递该方法到QueueUserWorkItem。方法InitializeCRMService没有输入参数。它只返回一个服务对象。请在下面的GetConnection方法中找到代码块:

public void GetConnection(string thread)
{
    ParallelOptions ops = new ParallelOptions();

    if(thread.Equals("one"))
    {
        Parallel.For(0, 1, i =>
        {
            dynamic serviceObject = InitializeCRMService();       
        });
    }
    else if (thread.Equals("multi"))
    {
        // HERE I NEED TO IMPLEMENT MULTITHREADING USING THREAD POOL 
        // AND NOT PARALLEL FOR LOOP......
        // ThreadPool.QueueUserWorkItem(new WaitCallback(InitializeCRMService));
    }
}

请注意我的方法InitializeCRMService()具有返回类型的服务对象。

请告诉我如何实施它。

1 个答案:

答案 0 :(得分:4)

由于你想在一个插槽可用时在ThreadPool中执行InitializeCRMService,而你只执行一次,解决方案取决于你想用InitializeCRMService的返回值做什么。

如果你只是想忽略它,到目前为止我有两个选择。


选项1

public void GetConnection(string thread)
{
    //I found that ops is not being used
    //ParallelOptions ops = new ParallelOptions();
    if(thread.Equals("one"))
    {
        Parallel.For(0, 1, i =>
        {
            //You don't really need to have a variable
            /*dynamic serviceObject =*/ InitializeCRMService();
        });
    }
    else if (thread.Equals("multi"))
    {
        ThreadPool.QueueUserWorkItem
        (
             new WaitCallback
             (
                 (_) =>
                 {
                      //You don't really need to have a variable
                      /*dynamic serviceObject =*/ InitializeCRMService();
                 }
             )
        );
    }
}

另一方面,如果你需要将它传递到某个地方来存储它,以后可以重复使用它,你可以这样做:

public void GetConnection(string thread)
{
    //I found that ops is not being used
    //ParallelOptions ops = new ParallelOptions();

    if(thread.Equals("one"))
    {
        Parallel.For(0, 1, i =>
        {
            //It seems to me a good idea to take the same path here too
            //dynamic serviceObject = InitializeCRMService();
            Store(InitializeCRMService());
        });
    }
    else if (thread.Equals("multi"))
    {
        ThreadPool.QueueUserWorkItem
        (
             new WaitCallback
             (
                 (_) =>
                 {
                      Store(InitializeCRMService());
                 }
             )
        );
    }
}

Store将是这样的:

private void Store(dynamic serviceObject)
{
    //store serviceObject somewhere you can use it later.
    //Depending on your situation you may want to
    // set a flag or use a ManualResetEvent to notify
    // that serviceObject is ready to be used.
    //Any pre proccess can be done here too.
    //Take care of thread affinity,
    // since this may come from the ThreadPool
    // and the consuming thread may be another one,
    // you may need some synchronization.
}

现在,如果您需要允许类的客户端访问serviceObject,您可以采用以下方法:

//Note: I marked it as partial because there may be other code not showed here
// in particular I will not write the method GetConnection again. That said...
// you can have it all in a single block in a single file without using partial.
public partial class YourClass
{
    private dynamic _serviceObject;

    private void Store(dynamic serviceObject)
    {
        _serviceObject = serviceObject;
    }

    public dynamic ServiceObject
    {
        get
        {
            return _serviceObject;
        }
    }
}

但这并不能解决所有情况。特别是如果你想让线程等待serviceObject准备就绪:

public partial class YourClass
{
    private ManualResetEvent _serviceObjectWaitHandle = new ManualResetEvent(false);
    private dynamic _serviceObject;


    private void Store(dynamic serviceObject)
    {
        _serviceObject = serviceObject;
        //If you need to do some work as soon as _serviceObject is ready...
        // then it can be done here, this may still be the thread pool thread.
        //If you need to call something like the UI...
        // you will need to use BeginInvoke or a similar solution.
        _serviceObjectWaitHandle.Set();
    }

    public void WaitForServiceObject()
    {
            //You may also expose other overloads, just for convenience.
            //This will wait until Store is executed
            //When _serviceObjectWaitHandle.Set() is called
            // this will let other threads pass.
            _serviceObjectWaitHandle.WaitOne();
    }

    public dynamic ServiceObject
    {
        get
        {
            return _serviceObject;
        }
    }
}

尽管如此,我还没有涵盖所有场景。对于intance ...如果多次调用GetConnection会发生什么?我们需要决定是否允许这样做,如果我们这样做,我们如何处理旧的serviceObject? (我们需要打电话来解雇它吗?)。如果我们允许多个线程一次调用GetConnection,这可能会有问题。所以默认情况下我会说我们没有,但我们不想阻止其他线程......

解决方案?如下:

//This is another part of the same class
//This one includes GetConnection
public partial class YourClass
{
    //1 if GetConnection has been called, 0 otherwise
    private int _initializingServiceObject;

    public void GetConnection(string thread)
    {
        if (Interlocked.CompareExchange(ref _initializingServiceObject, 1, 0) == 0)
        {
            //Go on, it is the first time GetConnection is called

            //I found that ops is not being used
            //ParallelOptions ops = new ParallelOptions();
            if(thread.Equals("one"))
            {
                Parallel.For(0, 1, i =>
                {
                    //It seems to me a good idea to take the same path here too
                    //dynamic serviceObject = InitializeCRMService();
                    Store(InitializeCRMService());
                });
            }
            else if (thread.Equals("multi"))
            {
                ThreadPool.QueueUserWorkItem
                (
                     new WaitCallback
                     (
                         (_) =>
                         {
                              Store(InitializeCRMService());
                         }
                     )
                );
            }
        }
    }
}

最后,如果我们允许多个线程使用_serviceObject,并且_serviceObject不是线程安全的,我们可能会遇到麻烦。使用监视器或使用读写锁是解决这个问题的两种选择。

你记得吗?

    public dynamic ServiceObject
    {
        get
        {
            return _serviceObject;
        }
    }

好的,你想让调用者在一个上下文中访问_serviceObject,这个上下文会阻止其他线程进入(参见System.Threading.Monitor),并确保它停止使用它,然后离开这个上下文我之前提到的。

现在考虑调用者线程仍然可以在某处存储_serviceObject的副本,然后保留syncrhonization,然后使用_serviceObject执行某些操作,并且当另一个线程正在使用它时可能会发生这种情况。

在线程方面,我习惯于考虑每个角落的情况。但是如果你可以控制调用线程,你可以很好地完成上面显示的属性。如果你不......让我们谈谈它,我警告你,它可能是广泛的。


选项2

这是一种完全不同的行为,在您的问题中提出的推荐Damien_The_Unbeliever让我觉得您可能打算返回serviceObject。在这种情况下,它不在线程之间共享,并且一次可以有多个serviceObject。并且所需的任何同步都留给调用者。

好的,这可能是你一直在寻找的东西:

public void GetConnection(string thread, Action<dynamic> callback)
{
    if (ReferenceEquals(callback, null))
    {
        throw new ArgumentNullException("callback");
    }
    //I found that ops is not being used
    //ParallelOptions ops = new ParallelOptions();
    if(thread.Equals("one"))
    {
        Parallel.For(0, 1, i =>
        {
            callback(InitializeCRMService());
        });
    }
    else if (thread.Equals("multi"))
    {
        ThreadPool.QueueUserWorkItem
        (
             new WaitCallback
             (
                 (_) =>
                 {
                      callback(InitializeCRMService());
                 }
             )
        );
    }
}

回调应该如何?好吧,只要它不在线程之间共享就可以了。为什么?因为每个调用GetConnection的线程都会传递它自己的回调Action,并且会收到一个不同的serviceObject,所以不存在一个线程对它做什么会影响另一个线程对它做什么的风险(因为它不是同一个serviceObject)。 p>

除非你想让一个线程调用它然后与其他线程共享它,在这种情况下,这是调用者的问题,它将在另一个地方在另一个时刻解决。


最后一点,您可以使用枚举来表示当前在字符串线程中传递的选项。事实上,因为你可以考虑使用bool只有两个选项,除非它们将来可能出现更多案例。