线程,任务和变量正确的方法

时间:2014-12-14 22:29:34

标签: c# multithreading task

我有一个web api方法启动可能需要很长时间的工作,所以我想将这项工作交给新线程并返回给调用者。

在下面的代码中,我定义了一个将启动n个工作流程的Action。我的问题涉及在Action方法之外定义的变量。

在Action中使用memberManager和workflowDefinitionName是否安全?

当InitiateBulkDischargeWorkflow方法立即返回时,memberManager和workflowDefinitionName会继续在Action代码块中可用吗?

我之前没有做过很多线程,因此我正在努力提高自己的知识,并且到目前为止我正在寻找有关代码的反馈。

示例代码:

public class MemberController : ApiController {
    private readonly IMemberManager memberManager;
    public MemberContoller(IMemberManager memberManager) {
        this.memberManager = memberManager;
    }

    protected int CurrentUserId { 
        get {
            // removed
        }
    }

    [System.Web.Http.HttpPost]
    public void InitiateBulkDischargeWorkflow(string workflowDefinitionName, IDictionary<string, object> args)
    {
        args["argInitiatorId"] = CurrentUserId;

        // removed - ensure args are valid

        Action<object> action = (x) =>
        {
            var args2 = (IDictionary<string, object>) x;
            foreach (var memberId in (List<int>)args["argMemberIds"]) {
                try {
                    // create new dictionary
                    var memberArgs = new Dictionary<string, object>();
                    memberArgs["argMemberId"] = memberId;
                    memberArgs["argInitiatorId"] = args2["argInitiatorId"];
                    // further dictionary configuration here

                    // long running process
                    memberManager.InitiateWorkflow(initiatorId, memberId, workflowDefinitionName, memberArgs);
                } catch (Exception e) {
                    log.ErrorFormat("Error initiating workflow '{0}' for memberid={1} by userid={2} in InitiateBulkDischargeWorkflow", e,
                        workflowDefinitionName, memberId, CurrentUserId);
                }
            }
        };

        // start thread
        System.Threading.Tasks.Task.Factory.StartNew(action, args);

        // return immediately, let thread handle creation of workflows
        return;
    }

2 个答案:

答案 0 :(得分:0)

如果代码编译,则变量可供使用。是否安全是另一回事。在你的问题中,鉴于到目前为止显示的代码,最好的人可以说是“它取决于”。

workflowDefinitionName变量可以肯定。变量本身是一个局部变量,不是由命名方法或内部的匿名方法修改的,而且对象是string,它是不可变的,本质上是线程安全的。

memberManager变量不同。变量本身,可能是MemberController类型的变量本身,尽管你的代码示例没有说明这一点,但是在匿名方法中可以像你一样使用它。但请注意,如果您有多个线程一次访问对象,则可能需要添加同步以确保安全发生。

如果没有更好的代码示例,我不能说需要什么样的同步。但是你可能需要为这个对象提供一些东西。

答案 1 :(得分:0)

您的代码似乎过于复杂。越复杂,它就越有可能出错。

当您使用值args2["argInitiatorId"]构建memberArgs字典时,您正在做一些奇怪的间接。 args2["argInitiatorId"]的值只是CurrentUserId,为什么不直接使用它?

此外,永远不要catch (Exception e)。它是引入错误并使代码可以解除代码的最佳方法之一。您应该只捕获您尝试处理的特定异常。

我的建议是让您的代码更简单:

public void InitiateBulkDischargeWorkflow(
    string workflowDefinitionName,
    IDictionary<string, object> args)
{
    var tasks =
        from memberId in args["argMemberIds"] as List<int>
        let memberArgs = new Dictionary<string, object>()
        {
            { "argMemberId", memberId },
            { "argInitiatorId", CurrentUserId },
        }
        select Task.Run(() =>
            memberManager.InitiateWorkflow(
                initiatorId,
                memberId,
                workflowDefinitionName,
                memberArgs));

    Task.WhenAll(tasks);
}

我希望这会有所帮助。