c#在对象构造函数中启动异步方法 - 不好的做法?

时间:2011-08-31 17:49:05

标签: c# .net asynchronous

我在类似于

的对象构造函数中有一些代码
delegate DataSet MyInvoker;

public MyObject(Param1 p1)
{
    // property sets here
    // ...

    BeginMyAsyncMethod();
}

public void BeginMyAsyncMethod() 
{
    // set some properties
    // ...

    MyInvoker inv = new MyInvoker(SomeBeginMethod);
    inv.BeginInvoke(new AsyncCallback(SomeEndMethod), null);
}

我的问题是:

  1. 这通常被视为不良做法吗?
  2. 在我的类中定义一个start方法(用户可以调用它来执行异步操作)会更好(或更好)吗?
  3. 这个answer给我的印象是,将它留给用户是不好的做法,虽然我特别谈到在构造函数中启动异步方法,而不是正确构造对象。

6 个答案:

答案 0 :(得分:22)

这可以通过稍微不同的方法轻松完成。在所有现实中,这种情况一直在发生,不是吗?这是一个简单的解决方案,为您提供一个选项,而不会做一些愚蠢的事情:

public class MyResource
{
    // do this instead of a constructor
    public async Task<MyResource> StartAsync()
    {
        await Task.Delay(1);
        return this;
    }
}

public class MyControl
{
    public MyResource Resource { get; set; }
    async void Button_Click(object s, EventArgs e)
    {
        // call start as if it is a constructor
        this.Resource = await new MyResource().StartAsync();
    }
}

答案 1 :(得分:13)

在构造函数中执行与创建对象实例无直接关系的操作通常不是一个好主意。例如,如果我不知道你的MyObject类的目的,我可能不知道它在创建新实例时会产生异步进程。这通常是一种不好的做法。

这几乎听起来像你在说;嘿,创建这个对象,它只在这个异步过程结束时才可用;这是违反直觉的。

如果你想做这样的事情,我认为通过工厂模式你肯定会得到更好的服务;你这样叫一个工厂,它会创建对象并为你调用方法:

 var instance = MyObjectBuilder.CreateInstance();

 // Internally, this does
 //    var x = new MyObject();
 //    x.Initilizatize();
 //    return x;

如果您的对象在完成初始化之前无法使用,那么您应该公开一个属性以检查它是否已准备好,如下所示:

 instance.WaitForReady(); // blocking version
 if(instance.IsReady) // non blocking check

答案 2 :(得分:8)

是的,因为您可能会引入意外行为。应该构造对象,不受异常和意外行为的影响。如果在构造对象并且用户试图使用它之后仍然发生异步操作......

此外,如果异步方法仍在运行并且用户决定销毁该对象,该怎么办?

好读:http://www.amazon.com/Framework-Design-Guidelines-Conventions-Libraries/dp/0321545613/ref=sr_1_1?s=books&ie=UTF8&qid=1314813265&sr=1-1

如果你的异步方法必须是异步的,因为它是一个长时间运行的进程,那么它需要被移动到它自己的方法,由用户调用,而不是在对象被实例化时。

如果用户在异步方法完成之前决定销毁它,那么你也可能会保留对该对象的引用,这意味着你可能会有内存泄漏。

如果要使用工厂或构建器创建初始化对象的HAS。

答案 3 :(得分:5)

在完全构造实例之前,在构造函数中启动任何可能在另一个线程上调用实例的东西绝对是个坏主意。即使它是你的构造函数的最后一行,也可以在子类构造函数完成运行之前调用回调,因为子类构造函数代码在基类构造函数代码之后运行。即使您的课程今天已被密封,未来也可能未开封,如果将来添加子类,追踪此类问题很可能会非常昂贵。

基本上,如果你不想最终打猎heisenbugs,不要从构造函数中做过这类事情。相反,添加一个像你在问题#2中提到的“开始”方法。

答案 4 :(得分:5)

我同意,不好主意。 添加Initialize方法。 您还可以添加一个返回新对象的静态方法并执行Initialize方法。 通过这种方式,您还可以将构造函数保持为私有。

public class XXX
{
    private XXX()
    {
    }

    private void DoMyWeirdThingsAsynchronously()
    {
        ....
    }

    public static XXX PerformSomethingStrange()
    {
        XXX result = new XXX();
        result.DoMyWeirdThingsAsynchronously();
        return result;
    }
}

答案 5 :(得分:0)

我个人会避免将这些代码放在构造函数中。最好提供一种启动异步操作的特定方法。从构造函数中抛出异常(除了验证异常)是一个坏主意。