OpenQQ、工作流与并发冲突

普通用户可以忽略本节的内容。

本节的任务和上一节是一样的,但是本节将结合OpenQQ来实现。

之前介绍的工作流,都是一人负责个流程,有时工作流会更复杂一些,例如一项工作分为多个流程,每一个流程会有多人负责,此时就需要考虑“抢先”的问题。例如A、B、C三人都有权限处理某一流程,但同一时候只能有一人处理,假定A用户已经“抢先”处理此流程,当B用户和C用户尝试处理同一流程的时候,系统必须能够通知两位后来者:已经有人在处理此流程了 ,这就是所谓的并发冲突。

本节我们用一个简单例子来说明如何将工作流和并发冲突相结合,其中并发冲突的处理技巧,和数据源无关,正因为不需要借助特定数据源的功能,所以通用性很强。

假定一个表有九列,分成三个工作流程来处理:

流程1处理一、二、三列
流程2处理四、五、六列
流程3处理七、八、九列

只有在上一流程已经处理完毕的情况下,才可以处理下一流程,例如只有输入一、二、三列的值后,才能输入四、五、六列的值。
本次设计要完成的任务为:

1、在针对某行处理一个流程前,首先自动检索该行的最新数据和流程处理进度,以决定能否处理此流程。
2、如果能够处理此流程,那么在开始处理之前先锁定此行,使得其他用户不能再处理此行,避免出现多个用户同时处理某一行的情况。
3、用户在处理完一个流程后,在保存处理结果的同时,还应该解锁行,使得其他用户可以处理此行。
4、提供流程回退功能,可以将某行回退到任意流程。

提示:

1、这里的锁定行,和我们之间介绍的锁定行是不同的,这里是在后台锁定某个行,使得其他用户不能编辑此行。
2、本节采用OpenQQ解决并发问题,不会出现死锁情况。

服务端的设计

1、在服务端项目的全局代码中加上代码:

Public
pdids As New Dictionary(Of Integer, String)

这里定义了一个字典,键值是行的主键,值是正在处理这行的用户名。

2、在服务端的OpenQQ服务端事件ReceivedMessage中编写代码:

Dim msg As String = e.Message
If
msg.StartsWith("%i") AndAlso msg.EndsWith("i%") Then '收到申请信号
    msg = msg.SubString(2, msg.Length - 4)
    Dim id As Integer
    If Integer.TryParse(msg,id) Then
        If pdids.ContainsKey(id) Then '
如果有人正在处理此行
            e.ReturnValue = pdids(id) '
通知申请人是谁在处理此行
        Else
            e.ReturnValue = "OK" '
通知申请人可以处理此行
            pdids.Add(id, e.UserName) '
登记此行 的处理者为申请者
        End If
    End If

ElseIf
msg.StartsWith("@i") AndAlso msg.EndsWith("i@") Then '收到处理完成信号
    msg = msg.SubString(2, msg.Length - 4)
    Dim id As Integer
    If Integer.TryParse(msg,id) Then
        If pdids.ContainsKey(id) Then
            pdids.Remove(id)
'从字典中移除此行的编辑登记
       
End If
    End If

End
If

3、在服务端的OpenQQ服务端事件UserLogout中编写代码:

Dim keys As New List(of Integer)
For
Each key As Integer In pdids.Keys
    If pdids(key) = e.UserName Then
        keys.Add(key)
    End If

Next
For
Each key As Integer In keys
    pdids.Remove(key)

Next

这样当有用户退出登录时,不管他是正常退出还是异常退出,都可以将字典中该用户的编辑登记移除,避免死锁。
客户端用户异常退出时,服务端的编辑登记的清除会有一个延时,时长取决于QQServer的
HeartbeatTimeout(心跳超时)属性。
上述编码有一个地方需要注意,我们不能在遍历字典和集合的过程中移除其成员(运行时会报错),所以我们用一个临时的集合Keys,先将要移除的键值保存在这个集合中,最后遍历这个集合中的键值,从字典
pdids中移除这些键值。

客户端的设计

1、首先我们需要加一个整数型的标记列, 标记列的值等于1、2、3的时候,分别表示已经处理完成流程1、流程2、流程3。
2、将表锁定,避免用户直接在表中输入数据
3、增加一个选择流程的窗口,窗口的设计如下:

按钮名称 代码
流程一

If QQClient.Ready = False Then
    PopMessage("请先启东QQClient","提示",PopIconEnum.Infomation,5)
    Return

End
If
Dim
r As Row = Tables("表A").Current
r
.DataRow.Load() '重新加载此行的数据
If
r.IsNull("标记") Then
    Dim msg As String = QQClient.SendWait("%i" & r("_Identify") & "i%")
    If msg = "OK" Then
        Forms("流程1").Open() '开始处理流程1
    ElseIf msg > "" Then
        PopMessage(msg & "正在处理此行","提示",PopIconEnum.Infomation,5)
    Else
        PopMessage("服务器无响应","提示",PopIconEnum.Infomation,5)
    End If

Else

    PopMessage("流程1已经处理完成","提示",PopIconEnum.Infomation,5)

End
If

流程二

If  QQClient.Ready = False Then
    PopMessage("请先启东QQClient","提示",PopIconEnum.Infomation,5)
    Return

End
If
Dim
r As Row = Tables("表A").Current
r
.DataRow.Load() '重新加载此行的数据
If
r.IsNull("标记") Then
    MessageBox.Show("流程1没有完成!")

ElseIf
r("标记") = 1 Then
   
Dim msg As String = QQClient.SendWait("%i" & r("_Identify") & "i%")
    If msg = "OK" Then
        Forms("
流程2").Open() '开始处理流程2
    ElseIf msg > "" Then
        PopMessage(msg & "
正在处理此行","提示",PopIconEnum.Infomation,5)
   
Else
        PopMessage("服务器无响应","提示",PopIconEnum.Infomation,5)
    End If

Else

    MessageBox.Show("流程2已经完成!")

End
If

流程三

If  QQClient.Ready = False Then
    PopMessage("请先启东QQClient","提示",PopIconEnum.Infomation,5)
    Return

End
If
Dim
r As Row = Tables("表A").Current
r
.DataRow.Load() '重新加载此行的数据
If
r.IsNull("标记") OrElse r("标记") = 1 Then
    MessageBox.Show("流程2没有完成!")

ElseIf
r("标记") = 2 Then
    Dim msg As String = QQClient.SendWait("%i" & r("_Identify") & "i%")
    If msg = "OK" Then
        Forms("流程3").Open() '开始处理流程3
    ElseIf msg > "" Then
        PopMessage(msg & "正在处理此行","提示",PopIconEnum.Infomation,5)
    Else
        PopMessage("服务器无响应","提示",PopIconEnum.Infomation,5)
    End If

Else

    MessageBox.Show("流程3已经完成!")

End
If

流程回退(1) '回退到流程1
Dim dr As DataRow = Tables("表A").Current.DataRow
Dim
cmd as New SQLCommand
cmd.CommandText =
"Update {表A} Set 标记 = 1 Where [_Identify] = " & dr("_Identify")
cmd.ExecuteNonQuery
dr.Load()
流程回退(2) '回退到流程2
Dim dr As DataRow = Tables("表A").Current.DataRow
Dim
cmd as New SQLCommand
cmd.CommandText =
"Update {表A} Set 标记 = 2 Where [_Identify] = " & dr("_Identify")
cmd.ExecuteNonQuery
dr.Load()
流程回退(3) '回退到流程3
Dim dr As DataRow = Tables("表A").Current.DataRow
Dim
cmd as New SQLCommand
cmd.CommandText =
"Update {表A} Set 标记 = 3 Where [_Identify] = " & dr("_Identify")
cmd.ExecuteNonQuery
dr.Load()
流程回退(Null) '回退到初始状态
Dim dr As DataRow = Tables("表A").Current.DataRow
Dim
cmd as New SQLCommand
cmd.CommandText =
"Update {表A} Set 标记 = Null Where [_Identify] = " & dr("_Identify")
cmd.ExecuteNonQuery

dr.Load()

4、增加三个模式窗口,分别为流程1、流程2、流程3,用于处理各个流程,每个流程窗口的设计都是类似的,用于输入该流程 应该处理的数据,例如流程2窗口的设计为:

由于表是锁定的,所以流程窗口中文本框的只读属性应该设置为False,否则不能输入数据:

窗口的BeforeClose事件代码设置代码为:

QQClient.Send("@i" & Tables("表A").Current("_Identify") & "i@") '通知服务器处理完成

这样关闭窗口前,会通知服务器移除此行的编辑登记,这样其他用户就可以处理这行了。

最后给流程窗口的按钮设置代码,以流程2窗口为例:

按钮名称 代码
确定 With Tables("表A").Current
   
If .Isnull("第四列") OrElse .IsNull("第五列") OrElse .IsNull("第六列") Then
        MessageBox.Show(
"流程二没有处理完!")
       
Return
    End
If
End
With
Tables
("表A").Current("标记") = 2
DataTables("表A").Save()
e.Form.Close()
取消 Tables("表A").Current.Reject()
Tables
("表A").Current("标记") = 1
DataTables
("表A").Save()
e.Form.Close()


本页地址:http://www.foxtable.com/webhelp/scr/3010.htm